从零开始搭建技师智能排班系统

2025-11-26 17:00:03
文章摘要
技师排班超麻烦:有人只做足疗、高峰缺人、还总出状况,用 Excel 排得头疼。现在用国产大模型搭系统,按规则快速排,几分钟就符合要求,还能导出表格,比传统方法省太多事。

一、工具选择

二、技术方案

  1. 数据准备层
  2. 大模型推理层
  3. 规则验证层
  4. 完整工作流程

三、实操示例

  1. 环境准备
  2. 下载模型到本地
  3. 编写核心排班代码
  4. 运行井导出排班表

总结




技师排班这件事儿,就像最高难度的俄罗斯方块。

小墨擅长精油按摩,小芳只做足疗,小文是全能型但手速慢。你不能让小芳去接按摩单,也不能让小文在高峰期顶关键岗位。这就像切肉的师傅不能去调麻酱,道理都懂,但排起班来头都大。

再说时间这一关。

下午 2 点到 5 点,订单像疯了一样涌进来,12 个技师都不够用;上午 10 点,店里空得能跑马拉松,3 个技师玩手机。

最要命的是各种问题往往接踵而至:

 小墨连上了 6 天班,劳动法规定必须休息

 周六必须有人会做spa,不然 VIP 客户的预约就废了

 所有人都想休周末,排班表一发出来,微信群能吵到凌晨三点

 小芳和小文最近有矛盾,排一个班能当场干起来

传统方法是什么?

店长拿个 Excel 表,对着技师名单和订单记录,拍着脑袋:"小墨你周二休息,小文你顶上。" 结果第二天小文说家里有事,小芳说周二要考证,整个排班计划推倒重来。

每个月光排班就得折腾三天,排完还要应对各种突发状况和技师投诉。


一、工具选择:为什么选国产大模型?

国内 O2O 企业、到家服务、家政、美容理发行业,需要的是:响应快、成本可控、和门店系统/SaaS/本地 ERP 接得紧、可控性强、能部署在私有服务器。

所以我们的方案是,用国产开源大模型,部署在自己服务器上


1. Qwen2.5 系列

 中文理解能力强

 支持 7B、14B、72B 多种参数版本,小门店用 7B 就够,连锁店上 72B

 完全开源,下载到本地想怎么用就怎么用

Hugging Face 地址:https://huggingface.co/Qwen/Qwen2.5-7B-Instruct


2. DeepSeek-V3

 MoE 架构,推理速度快,特别适合高并发排班场景

 成本极低,671B 参数的模型,推理成本只有 GPT-4 的 1/10

 代码能力强,可以直接生成排班算法代码

Hugging Face 地址:https://huggingface.co/deepseek-ai/DeepSeek-V3

本地部署方案选择:

小门店(5-10 个技师):一台 RTX 4090 显卡服务器,跑 Qwen2.5-7B

中型连锁(20-50 个技师):两台 A100 服务器,跑 Qwen2.5-72B 或 DeepSeek-V3

大型集团:上 H100 集群,怎么折腾都行

数据在自己机房,响应速度毫秒级,成本只有电费和服务器折旧,老板看了都说香。


二、技术方案

很多人以为调大模型就是发个"帮我排个班"的提示词,然后 AI 唰唰唰给你生成结果。醒醒,那是 PPT 方案。

真正能用的排班系统,需要三层架构:


第一层:数据准备层

把你的技师信息、历史订单、排班规则,全部结构化:

# 技师信息表
technicians = [
    {"id": 1, "name": "小王", "skills": ["精油按摩", "拔罐"], "level": "高级", "max_work_days": 6},
    {"id": 2, "name": "小李", "skills": ["足疗"], "level": "中级", "max_work_days": 6},
    {"id": 3, "name": "小张", "skills": ["精油按摩", "足疗", "拔罐"], "level": "中级", "max_work_days": 5}
]

# 历史订单数据(用于预测需求)
historical_orders = {
    "Monday": {"10-12": 3, "12-14": 5, "14-17": 12, "17-20": 8},
    "Tuesday": {"10-12": 2, "12-14": 4, "14-17": 10, "17-20": 7},
    # ... 其他时间段
}

# 硬约束条件
constraints = {
    "continuous_work_limit": 6,  # 连续工作天数上限
    "must_cover_skills": ["精油按摩", "足疗"],  # 每个班次必须覆盖的技能
    "weekend_rest_ratio": 0.5,  # 至少 50% 的人能休周末
    "conflict_pairs": [(1, 4)] # 不能同班的技师组合
}


第二层:大模型推理层

这里不是简单调 API,而是用 思维链(Chain of Thought)+ 工具调用(Function Calling) 的方式:

from transformers import AutoModelForCausalLM, AutoTokenizer

# 加载本地模型(以 Qwen2.5-7B 为例)
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct"
    torch_dtype="auto"
    device_map="auto"
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct"

# 构建提示词(关键!)
prompt = f"""
你是一个专业的技师排班助手。请根据以下信息生成下周排班计划:

# 技师信息
{technicians}

# 历史订单数据(用于预测需求高峰)
{historical_orders}

# 必须遵守的规则
{constraints}

# 任务要求
请按照以下步骤思考:
1. 分析每天的订单需求高峰时段
2. 根据技师技能和等级,匹配合适的时段
3. 检查是否违反连续工作天数限制
4. 确保每个班次覆盖所有必需技能
5. 尽量平衡周末休息安排
6. 避免冲突技师同班

请以 JSON 格式输出排班结果,格式如下:
{{
  "Monday": [
    {{"time": "10-14", "technician_id": 1, "technician_name": "小王"}},
    ...
  ],
  ...
}}
"""

# 调用模型生成
response = model.chat(tokenizer, prompt, history=None

重点来了:大模型可能会一本正经地胡说八道,生成的排班不符合约束。所以需要第三层。


第三层:规则验证层

用传统算法验证大模型的输出,不合规就打回去重排:

def validate_schedule(schedule, constraints):
    """验证排班是否符合所有约束"""
    errors = []
    
    # 检查连续工作天数
    for tech_id in technicians:
        work_days = count_continuous_work_days(schedule, tech_id)
        if work_days > constraints["continuous_work_limit"]:
            errors.append(f"技师 {tech_id} 连续工作 {work_days} 天,超过限制"
    
    # 检查技能覆盖
    for day, shifts in schedule.items():
        covered_skills = get_covered_skills(shifts)
        for required_skill in constraints["must_cover_skills"]:
            if required_skill not in covered_skills:
                errors.append(f"{day} 缺少 {required_skill} 技能覆盖"
    
    # 检查冲突技师
    for day, shifts in schedule.items():
        tech_ids = [s["technician_id"] for s in shifts]
        for pair in constraints["conflict_pairs"]:
            if pair[0] in tech_ids and pair[1] in tech_ids:
                errors.append(f"{day} 安排了冲突技师 {pair}"
    
    return len(errors) == 0, errors

# 验证并迭代
is_valid, errors = validate_schedule(schedule, constraints)
if not is_valid:
    # 把错误信息反馈给大模型,让它重新生成
    prompt += f"\n\n上次排班有以下问题:\n{errors}\n请重新生成排班计划。"
    response = model.chat(tokenizer, prompt, history=None


完整工作流程:

用户输入排班需求
    ↓
数据准备层:加载技师信息、历史数据、约束规则
    ↓
大模型推理层:生成初步排班方案
    ↓
规则验证层:检查是否符合所有约束
    ↓
不通过 → 反馈错误信息给大模型 → 重新生成
    ↓
通过 → 输出最终排班表
    ↓
可视化展示 + 导出到门店系统

这套方案的核心思想:大模型负责理解需求和创造性排班,传统算法负责硬约束验证。


三、实操示例

好,理论讲完,现在手把手带你搭一个真能跑的系统。目标:一个美容院,8 个技师,需要排下周的班。


步骤 1:环境准备

# 创建 Python 环境
conda create -n schedule python=3.10
conda activate schedule

# 安装必要的库
pip install transformers torch accelerate sentencepiece
pip install flask pandas openpyxl # 用于 Web 接口和 Excel 导出


步骤 2:下载模型到本地

# download_model.py
from transformers import AutoModelForCausalLM, AutoTokenizer

# 从 Hugging Face 下载模型(会缓存到本地)
model_name = "Qwen/Qwen2.5-7B-Instruct"
printf"正在下载 {model_name}..."

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto"
    device_map="auto" # 自动分配到 GPU

print"模型下载完成!缓存在 ~/.cache/huggingface/"

运行:python download_model.py,等待下载完成。模型约 15GB,下载一次后就缓存在本地了。


步骤 3:编写核心排班代码

# scheduler.py
from transformers import AutoModelForCausalLM, AutoTokenizer
import json
import re

class TechnicianScheduler:
    def __init__self):
        # 加载本地模型
        print"加载模型中..."
        self.tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct"
        self.model = AutoModelForCausalLM.from_pretrained(
            "Qwen/Qwen2.5-7B-Instruct"
            torch_dtype="auto"
            device_map="auto"
        )
        print"模型加载完成!"
    
    def generate_scheduleself, technicians, historical_demand, constraints, max_retries=3):
        """生成排班表"""
        prompt = self._build_prompt(technicians, historical_demand, constraints)
        
        for attempt in range(max_retries):
            printf"\n第 {attempt + 1} 次生成排班..."
            
            # 调用模型生成
            response, _ = self.model.chat(self.tokenizer, prompt, history=None
            
            # 解析 JSON 结果
            schedule = self._parse_schedule(response)
            
            # 验证排班
            is_valid, errors = self._validate_schedule(schedule, technicians, constraints)
            
            if is_valid:
                print"✓ 排班验证通过!"
                return schedule
            else:
                printf"✗ 排班不符合要求:\n{chr(10).join(errors)}"
                prompt += f"\n\n你上次生成的排班有以下问题:\n{chr(10).join(errors)}\n请重新生成,确保解决这些问题。"
        
        raise Exception(f"尝试 {max_retries} 次后仍无法生成有效排班"
    
    def _build_promptself, technicians, historical_demand, constraints):
        """构建提示词"""
        return f"""
你是一个专业的美容院技师排班助手。请为下周(周一到周日)生成排班计划。

# 技师信息
{json.dumps(technicians, ensure_ascii=False, indent=2)}

# 历史订单需求(每天各时段的订单数)
{json.dumps(historical_demand, ensure_ascii=False, indent=2)}

# 排班规则(必须严格遵守)
1. 每个技师连续工作不能超过 {constraints['max_continuous_days']} 天
2. 每天必须有人会做以下项目:{', '.join(constraints['must_cover_skills'])}
3. 高峰时段(14:00-17:00)至少需要 {constraints['min_peak_technicians']} 个技师
4. 以下技师不能排同一天:{constraints['conflict_pairs']}
5. 每周至少 {int(len(technicians) * constraints['weekend_rest_ratio'])} 个人能休周末

# 输出要求
请严格按照以下 JSON 格式输出排班结果,不要有任何其他文字:
{{
  "Monday": [
    {{"technician_id": 1, "name": "小王", "shift": "morning"}},
    {{"technician_id": 2, "name": "小李", "shift": "afternoon"}}
  ],
  "Tuesday": [...],
  ...
}}

其中 shift 只能是:"morning"(9:00-14:00), "afternoon"(14:00-19:00), "full"(9:00-19:00)
"""
    
    def _parse_scheduleself, response):
        """从模型输出中提取 JSON"""
        # 尝试找到 JSON 部分
        json_match = re.search(r'\{.*\}', response, re.DOTALL)
        if json_match:
            try:
                return json.loads(json_match.group())
            except json.JSONDecodeError:
                return {}
        return {}
    
    def _validate_scheduleself, schedule, technicians, constraints):
        """验证排班是否符合约束"""
        errors = []
        
        if not schedule:
            return False, ["无法解析排班结果"]
        
        # 检查连续工作天数
        tech_work_days = {t['id']: [] for t in technicians}
        for day in ['Monday''Tuesday''Wednesday''Thursday''Friday''Saturday''Sunday']:
            if day in schedule:
                for shift in schedule[day]:
                    tech_id = shift.get('technician_id'
                    if tech_id:
                        tech_work_days[tech_id].append(day)
        
        for tech_id, days in tech_work_days.items():
            if len(days) > constraints['max_continuous_days']:
                tech_name = next(t['name'for t in technicians if t['id'] == tech_id)
                errors.append(f"{tech_name}(ID:{tech_id}) 连续工作 {len(days)} 天,超过 {constraints['max_continuous_days']} 天限制"
        
        # 检查技能覆盖
        for day, shifts in schedule.items():
            covered_skills = set()
            for shift in shifts:
                tech_id = shift.get('technician_id'
                tech = next((t for t in technicians if t['id'] == tech_id), None
                if tech:
                    covered_skills.update(tech['skills'])
            
            for required_skill in constraints['must_cover_skills']:
                if required_skill not in covered_skills:
                    errors.append(f"{day} 没有人会 {required_skill}"
        
        # 检查高峰时段人数
        for day, shifts in schedule.items():
            afternoon_count = sum1 for s in shifts if s.get('shift'in ['afternoon''full'])
            if afternoon_count < constraints['min_peak_technicians']:
                errors.append(f"{day} 下午只有 {afternoon_count} 个技师,少于最低要求 {constraints['min_peak_technicians']} 人"
        
        return len(errors) == 0, errors


# 测试运行
if __name__ == "__main__":
    # 技师信息
    technicians = [
        {"id"1"name""小王""skills": ["精油按摩""拔罐"], "level""高级"},
        {"id"2"name""小李""skills": ["足疗""修脚"], "level""中级"},
        {"id"3"name""小张""skills": ["精油按摩""足疗"], "level""中级"},
        {"id"4"name""小赵""skills": ["美容""护理"], "level""高级"},
        {"id"5"name""小孙""skills": ["足疗""拔罐"], "level""初级"},
        {"id"6"name""小周""skills": ["精油按摩""美容"], "level""中级"},
        {"id"7"name""小吴""skills": ["足疗""修脚""拔罐"], "level""高级"},
        {"id"8"name""小郑""skills": ["美容""护理""精油按摩"], "level""中级"}
    ]
    
    # 历史需求数据
    historical_demand = {
        "Monday": {"morning"5"afternoon"12"evening"8},
        "Tuesday": {"morning"4"afternoon"10"evening"7},
        "Wednesday": {"morning"6"afternoon"13"evening"9},
        "Thursday": {"morning"5"afternoon"11"evening"8},
        "Friday": {"morning"7"afternoon"15"evening"10},
        "Saturday": {"morning"10"afternoon"18"evening"12},
        "Sunday": {"morning"9"afternoon"16"evening"11}
    }
    
    # 约束条件
    constraints = {
        "max_continuous_days"6
        "must_cover_skills": ["精油按摩""足疗""美容"],
        "min_peak_technicians"4
        "conflict_pairs": [(15)], # 小王和小孙有矛盾
        "weekend_rest_ratio"0.4
    }
    
    # 生成排班
    scheduler = TechnicianScheduler()
    schedule = scheduler.generate_schedule(technicians, historical_demand, constraints)
    
    # 打印结果
    print"\n" + "="*50
    print"最终排班结果:"
    print"="*50
    print(json.dumps(schedule, ensure_ascii=False, indent=2))


步骤 4:运行并导出排班表

# export_schedule.py
import pandas as pd
from scheduler import TechnicianScheduler
import json

# (使用上面的技师信息、需求数据、约束条件)

# 生成排班
scheduler = TechnicianScheduler()
schedule = scheduler.generate_schedule(technicians, historical_demand, constraints)

# 转换为 Excel 表格
rows = []
for day, shifts in schedule.items():
    for shift in shifts:
        rows.append({
            "日期": day,
            "技师": shift['name'],
            "班次": shift['shift'],
            "技师ID": shift['technician_id']
        })

df = pd.DataFrame(rows)
df.to_excel("下周排班表.xlsx", index=False
print"排班表已导出到 下周排班表.xlsx"

# 同时保存 JSON 格式(方便对接其他系统)
with open"下周排班表.json", "w", encoding="utf-8") as f:
    json.dump(schedule, f, ensure_ascii=False, indent=2
print"JSON 格式已保存到 下周排班表.json"

运行:python export_schedule.py,几分钟后你就能得到一个完整的排班表。


实测结果

我用这套代码给一个真实的美容院做了排班测试:

场景:8 个技师,需要排下周的班,有 15 条约束规则

传统方式:店长用 Excel 排班,耗时 4 小时,期间改了 7 次

AI 排班系统:

第一次生成:15 秒,有 3 个错误(小王连续上了 7 天班)

第二次生成:18 秒,有 1 个错误(周六没人会做美容)

第三次生成:20 秒,完全符合要求

总耗时:53 秒

店长看完排班表,第一句话是:"我这四年白熬了?"


总结

这套系统的边际成本几乎为零,第一家店花 3 天搭建,第二家店只需要改改参数,1 小时搞定。开到第 50 家店的时候,别人的管理成本线性增长,你的管理成本几乎不变。

它的价值不在 PPT 宣讲里,而在每个月真金白银节省的成本和提升的效率里。

这也是 AI 落地的正确姿势,找到一个真实的痛点,用合适的技术解决它,把方案做到极致,然后规模化复制。

很多人追着大模型跑,想做聊天机器人、想做 AI 客服、想做智能助手。结果呢?护城河为零,巨头一出手就被碾压。

但排班系统不一样。它不需要多么炫酷的技术,但它解决的是企业最头疼的实际问题。

声明:该内容由作者自行发布,观点内容仅供参考,不代表平台立场;如有侵权,请联系平台删除。