引言:为什么需要智能假期规划解决方案

在现代快节奏的生活中,假期规划已经成为许多人每年必须面对的重要任务。然而,传统的假期规划方式往往存在诸多痛点:手动查询节假日信息繁琐耗时、排期冲突难以发现、最佳出行时间难以确定、跨年假期规划复杂等。特别是对于需要兼顾工作安排、家庭需求和个人偏好的用户来说,一个全面的假期规划解决方案显得尤为重要。

本文将详细介绍一个集排期预测与节日放假时间查询于一体的一站式解决方案,通过智能化的算法和便捷的查询功能,帮助用户轻松规划假期,最大化假期利用效率。

系统架构概述

核心功能模块

  1. 节日放假时间查询模块

    • 支持中国大陆法定节假日查询
    • 包含调休安排信息
    • 提供历史和未来节假日数据
    • 支持按年份、月份筛选
  2. 智能排期预测模块

    • 基于历史数据的假期使用趋势分析
    • 最佳出行时间推荐算法
    • 个人假期余额管理
    • 冲突检测与提醒
  3. 一站式集成平台

    • 统一的用户界面
    • 个性化设置与偏好管理
    • 数据导出与分享功能
    • 多平台支持(Web、移动端)

节日放假时间查询模块实现

数据结构设计

首先,我们需要定义节假日的数据结构。在中国大陆,节假日通常包括固定的法定节假日和根据年份调整的调休安排。

from datetime import datetime, date, timedelta
from typing import List, Dict, Optional
import json

class Holiday:
    """节假日数据类"""
    def __init__(self, name: str, date: date, is_lunar: bool = False, 
                 is_legal: bool = True, duration: int = 1, 
                 adjustment_days: List[date] = None):
        """
        初始化节假日对象
        :param name: 节假日名称
        :param date: 节假日日期
        :param is_lunar: 是否为农历节日
        :param is_legal: 是否为法定节假日
        :param duration: 持续天数
        :param adjustment_days: 调休日期列表
        """
        self.name = name
        self.date = date
        self.is_lunar = is_lunar
        self.is_legal = is_legal
        self.duration = duration
        self.adjustment_days = adjustment_days or []

    def __repr__(self):
        return f"Holiday(name='{self.name}', date={self.date}, duration={self.duration})"

    def to_dict(self):
        """转换为字典格式"""
        return {
            'name': self.name,
            'date': self.date.isoformat(),
            'is_lunar': self.is_lunar,
            'is_legal': self.is_legal,
            'duration': self.duration,
            'adjustment_days': [d.isoformat() for d in self.adjustment_days]
        }

节假日数据管理器

接下来,我们创建一个节假日管理器,用于存储和查询节假日信息:

class HolidayManager:
    """节假日管理器"""
    def __init__(self):
        self.holidays: Dict[int, List[Holiday]] = {}  # 按年份存储
        self._initialize_data()

    def _initialize_data(self):
        """初始化中国大陆法定节假日数据(2020-2025年)"""
        # 2024年节假日数据(示例)
        self.holidays[2024] = [
            Holiday("元旦", date(2024, 1, 1), duration=1),
            Holiday("春节", date(2024, 2, 10), duration=7, 
                    adjustment_days=[date(2024, 2, 4), date(2024, 2, 18)]),
            Holiday("清明节", date(2024, 4, 4), duration=1),
            Holiday("劳动节", date(2024, 5, 1), duration=5,
                    adjustment_days=[date(2024, 4, 28), date(2024, 5, 11)]),
            Holiday("端午节", date(2024, 6, 10), duration=1),
            Holiday("中秋节", date(2024, 9, 17), duration=1),
            Holiday("国庆节", date(2024, 10, 1), duration=7,
                    adjustment_days=[date(2024, 9, 29), date(2024, 10, 12)])
        ]

        # 2025年节假日数据(预测)
        self.holidays[2025] = [
            Holiday("元旦", date(2025, 1, 1), duration=1),
            Holiday("春节", date(2025, 1, 29), duration=7,
                    adjustment_days=[date(2025, 1, 26), date(2025, 2, 8)]),
            Holiday("清明节", date(2025, 4, 4), duration=1),
            Holiday("劳动节", date(2025, 5, 1), duration=5,
                    adjustment_days=[date(2025, 4, 27), date(2025, 5, 10)]),
            Holiday("端午节", date(2025, 6, 19), duration=1),
            Holiday("中秋节", date(2025, 10, 6), duration=1),
            Holiday("国庆节", date(2025, 10, 1), duration=7,
                    adjustment_days=[date(2025, 9, 28), date(2025, 10, 11)])
        ]

    def get_holidays_by_year(self, year: int) -> List[Holiday]:
        """获取指定年份的所有节假日"""
        return self.holidays.get(year, [])

    def get_holidays_by_month(self, year: int, month: int) -> List[Holiday]:
        """获取指定月份的节假日"""
        return [h for h in self.get_holidays_by_year(year) if h.date.month == month]

    def get_next_holiday(self, from_date: Optional[date] = None) -> Optional[Holiday]:
        """获取下一个节假日"""
        if from_date is None:
            from_date = date.today()
        
        # 获取未来所有节假日
        upcoming_holidays = []
        for year in range(from_date.year, from_date.year + 2):
            for holiday in self.get_holidays_by_year(year):
                if holiday.date >= from_date:
                    upcoming_holidays.append(holiday)
        
        # 按日期排序并返回第一个
        upcoming_holidays.sort(key=lambda x: x.date)
        return upcoming_holidays[0] if upcoming_holidays else None

    def get_holiday_by_name(self, name: str, year: int) -> Optional[Holiday]:
        """根据名称和年份查找节假日"""
        for holiday in self.get_holidays_by_year(year):
            if name in holiday.name:
                return holiday
        return None

    def is_holiday(self, check_date: date) -> bool:
        """检查某天是否为节假日"""
        for holiday in self.get_holidays_by_year(check_date.year):
            if holiday.date == check_date:
                return True
            # 检查持续天数
            for i in range(holiday.duration):
                if holiday.date + timedelta(days=i) == check_date:
                    return True
        return False

    def is_workday(self, check_date: date) -> bool:
        """检查某天是否为工作日(考虑调休)"""
        # 如果是周末
        if check_date.weekday() >= 5:  # 5=周六, 6=周日
            # 检查是否为调休工作日
            for holiday in self.get_holidays_by_year(check_date.year):
                if check_date in holiday.adjustment_days:
                    return True
            return False
        # 如果是工作日
        else:
            # 检查是否为节假日
            return not self.is_holiday(check_date)

    def get_workdays_in_range(self, start_date: date, end_date: date) -> int:
        """计算日期范围内的工作日数量"""
        workdays = 0
        current_date = start_date
        while current_date <= end_date:
            if self.is_workday(current_date):
                workdays += 1
            current_date += timedelta(days=1)
        return workdays

    def export_holidays_json(self, year: int) -> str:
        """导出指定年份节假日为JSON"""
        holidays = self.get_holidays_by_year(year)
        return json.dumps([h.to_dict() for h in holidays], ensure_ascii=False, indent=2)

节假日查询接口

class HolidayQueryService:
    """节假日查询服务"""
    def __init__(self):
        self.manager = HolidayManager()

    def query_upcoming_holidays(self, count: int = 3) -> List[Dict]:
        """查询即将到来的节假日"""
        holidays = []
        from_date = date.today()
        
        while len(holidays) < count:
            next_holiday = self.manager.get_next_holiday(from_date)
            if not next_holiday:
                break
            holidays.append({
                'name': next_holiday.name,
                'date': next_holiday.date.isoformat(),
                'days_until': (next_holiday.date - date.today()).days,
                'duration': next_holiday.duration
            })
            from_date = next_holiday.date + timedelta(days=1)
        
        return holidays

    def query_holiday_calendar(self, year: int, month: Optional[int] = None) -> List[Dict]:
        """查询指定月份的节假日日历"""
        if month:
            holidays = self.manager.get_holidays_by_month(year, month)
        else:
            holidays = self.manager.get_holidays_by_year(year)
        
        return [{
            'name': h.name,
            'date': h.date.isoformat(),
            'duration': h.duration,
            'adjustment_days': [d.isoformat() for d in h.adjustment_days]
        } for h in holidays]

    def query_workday_status(self, date_str: str) -> Dict:
        """查询某天的工作状态"""
        try:
            check_date = datetime.strptime(date_str, "%Y-%m-%d").date()
            is_holiday = self.manager.is_holiday(check_date)
            is_workday = self.manager.is_workday(check_date)
            
            status = "工作日" if is_workday else "节假日"
            if is_holiday and not is_workday:
                status = "法定节假日"
            elif check_date.weekday() >= 5 and not is_workday:
                status = "周末"
            
            return {
                'date': date_str,
                'status': status,
                'is_workday': is_workday,
                'is_holiday': is_holiday,
                'weekday': check_date.strftime("%A")
            }
        except ValueError:
            return {'error': '日期格式错误,请使用 YYYY-MM-DD 格式'}

# 使用示例
if __name__ == "__main__":
    service = HolidayQueryService()
    
    # 查询即将到来的节假日
    print("=== 即将到来的节假日 ===")
    upcoming = service.query_upcoming_holidays(5)
    for h in upcoming:
        print(f"{h['name']}: {h['date']} (还有 {h['days_until']} 天)")
    
    # 查询2024年10月节假日
    print("\n=== 2024年10月节假日 ===")
    october_holidays = service.query_holiday_calendar(2024, 10)
    for h in october_holidays:
        print(f"{h['name']}: {h['date']} (持续 {h['duration']} 天)")
    
    # 查询某天状态
    print("\n=== 工作状态查询 ===")
    status = service.query_workday_status("2024-10-1")
    print(f"2024-10-1: {status['status']}")

智能排期预测模块实现

个人假期管理

from enum import Enum
from dataclasses import dataclass, field
from typing import List, Set, Optional
from datetime import date, datetime, timedelta
import statistics

class LeaveType(Enum):
    """假期类型"""
    ANNUAL = "年假"
    SICK = "病假"
    PERSONAL = "事假"
    MATERNITY = "产假"
    OTHER = "其他"

@dataclass
class LeaveRecord:
    """请假记录"""
    leave_type: LeaveType
    start_date: date
    end_date: date
    days: int
    reason: str = ""
    status: str = "approved"  # approved, pending, rejected

    @property
    def is_conflict(self, other: 'LeaveRecord') -> bool:
        """检查是否与其他请假冲突"""
        return (self.start_date <= other.end_date and 
                self.end_date >= other.start_date)

@dataclass
class PersonalLeaveProfile:
    """个人假期档案"""
    user_id: str
    annual_leave_total: int = 15  # 年假总额
    annual_leave_remaining: int = 15  # 剩余年假
    leave_history: List[LeaveRecord] = field(default_factory=list)
    preferred_travel_months: Set[int] = field(default_factory=lambda: {7, 8, 10})
    avoid_months: Set[int] = field(default_factory=lambda: {1, 2, 12})

    def add_leave_record(self, record: LeaveRecord) -> bool:
        """添加请假记录"""
        if record.days > self.annual_leave_remaining:
            return False
        
        # 检查冲突
        for existing in self.leave_history:
            if record.is_conflict(existing):
                return False
        
        self.leave_history.append(record)
        if record.leave_type == LeaveType.ANNUAL:
            self.annual_leave_remaining -= record.days
        return True

    def get_leave_stats(self) -> Dict:
        """获取假期使用统计"""
        if not self.leave_history:
            return {}
        
        # 按类型统计
        type_stats = {}
        for record in self.leave_history:
            if record.status == "approved":
                type_stats[record.leave_type.value] = type_stats.get(record.leave_type.value, 0) + record.days
        
        # 月份分布
        month_stats = {}
        for record in self.leave_history:
            if record.status == "approved":
                month = record.start_date.month
                month_stats[month] = month_stats.get(month, 0) + record.days
        
        return {
            'total_used': sum(r.days for r in self.leave_history if r.status == "approved"),
            'by_type': type_stats,
            'by_month': month_stats,
            'remaining': self.annual_leave_remaining
        }

最佳出行时间推荐算法

class VacationOptimizer:
    """假期优化器"""
    def __init__(self, holiday_manager: HolidayManager):
        self.holiday_manager = holiday_manager
        self.score_weights = {
            'holiday_overlap': 0.3,      # 与节假日重叠
            'personal_preference': 0.25, # 个人偏好
            'weather': 0.2,              # 天气因素
            'crowd_level': 0.15,         # 人流密度
            'cost': 0.1                  # 成本因素
        }

    def calculate_holiday_score(self, start_date: date, duration: int) -> float:
        """计算假期得分(越高越好)"""
        score = 0
        
        # 检查与法定节假日重叠
        for i in range(duration):
            current_date = start_date + timedelta(days=i)
            if self.holiday_manager.is_holiday(current_date):
                score += 10  # 重叠一天加10分
        
        # 检查是否需要调休
        workdays_before = self.holiday_manager.get_workdays_in_range(
            start_date - timedelta(days=5), start_date - timedelta(days=1)
        )
        workdays_after = self.holiday_manager.get_workdays_in_range(
            start_date + timedelta(days=duration), 
            start_date + timedelta(days=duration + 4)
        )
        
        # 调休越少越好
        if workdays_before <= 1 and workdays_after <= 1:
            score += 5
        
        return score

    def calculate_personal_preference_score(self, start_date: date, profile: PersonalLeaveProfile) -> float:
        """计算个人偏好得分"""
        score = 0
        
        # 月份偏好
        if start_date.month in profile.preferred_travel_months:
            score += 10
        elif start_date.month in profile.avoid_months:
            score -= 10
        
        # 避开已安排的假期
        for record in profile.leave_history:
            if record.is_conflict(LeaveRecord(LeaveType.ANNUAL, start_date, start_date, 1)):
                score -= 20
        
        return score

    def calculate_weather_score(self, start_date: date, duration: int) -> float:
        """计算天气得分(简化版)"""
        # 简单的季节性评分
        month = start_date.month
        if month in [3, 4, 5, 9, 10]:  # 春季和秋季
            return 10
        elif month in [6, 7, 8]:  # 夏季
            return 5
        elif month in [12, 1, 2]:  # 冬季
            return 3
        else:
            return 5

    def calculate_crowd_score(self, start_date: date, duration: int) -> float:
        """计算人流密度得分(越低越好,但这里反向评分)"""
        # 检查是否为节假日高峰期
        score = 10
        
        # 国庆、春节高峰期
        if start_date.month == 10 and start_date.day <= 3:
            score -= 5
        if start_date.month == 2 and start_date.day <= 5:
            score -= 5
        
        # 检查是否为周末
        if start_date.weekday() >= 5:
            score -= 2
        
        return max(score, 0)

    def calculate_cost_score(self, start_date: date, duration: int) -> float:
        """计算成本得分"""
        # 简单的成本模型:旺季成本高,淡季成本低
        month = start_date.month
        if month in [7, 8, 10]:  # 旺季
            return 3
        elif month in [12, 1, 2]:  # 淡季
            return 10
        else:
            return 7

    def recommend_vacation_slots(self, profile: PersonalLeaveProfile, 
                                year: int, 
                                duration: int,
                                max_results: int = 5) -> List[Dict]:
        """推荐最佳假期时间段"""
        recommendations = []
        
        # 遍历全年
        start_date = date(year, 1, 1)
        end_date = date(year, 12, 31)
        
        current_date = start_date
        while current_date <= end_date - timedelta(days=duration - 1):
            # 检查是否可用
            can_use = True
            for i in range(duration):
                check_date = current_date + timedelta(days=i)
                if not self.holiday_manager.is_workday(check_date):
                    can_use = False
                    break
                
                # 检查个人请假冲突
                for record in profile.leave_history:
                    if record.is_conflict(LeaveRecord(LeaveType.ANNUAL, check_date, check_date, 1)):
                        can_use = False
                        break
            
            if can_use:
                # 计算综合得分
                holiday_score = self.calculate_holiday_score(current_date, duration)
                personal_score = self.calculate_personal_preference_score(current_date, profile)
                weather_score = self.calculate_weather_score(current_date, duration)
                crowd_score = self.calculate_crowd_score(current_date, duration)
                cost_score = self.calculate_cost_score(current_date, duration)
                
                total_score = (
                    holiday_score * self.score_weights['holiday_overlap'] +
                    personal_score * self.score_weights['personal_preference'] +
                    weather_score * self.score_weights['weather'] +
                    crowd_score * self.score_weights['crowd_level'] +
                    cost_score * self.score_weights['cost']
                )
                
                recommendations.append({
                    'start_date': current_date.isoformat(),
                    'end_date': (current_date + timedelta(days=duration - 1)).isoformat(),
                    'duration': duration,
                    'score': round(total_score, 2),
                    'details': {
                        'holiday_score': holiday_score,
                        'personal_score': personal_score,
                        'weather_score': weather_score,
                        'crowd_score': crowd_score,
                        'cost_score': cost_score
                    }
                })
            
            current_date += timedelta(days=1)
        
        # 按得分排序
        recommendations.sort(key=lambda x: x['score'], reverse=True)
        return recommendations[:max_results]

冲突检测与提醒系统

class ConflictDetector:
    """冲突检测器"""
    def __init__(self, holiday_manager: HolidayManager):
        self.holiday_manager = holiday_manager

    def detect_conflicts(self, start_date: date, end_date: date, 
                        profile: PersonalLeaveProfile) -> Dict:
        """检测指定日期范围内的所有冲突"""
        conflicts = {
            'holiday_conflicts': [],
            'workday_conflicts': [],
            'personal_conflicts': [],
            'weekend_conflicts': [],
            'adjustment_conflicts': []
        }

        current_date = start_date
        while current_date <= end_date:
            # 检查是否为节假日
            if self.holiday_manager.is_holiday(current_date):
                conflicts['holiday_conflicts'].append(current_date.isoformat())
            
            # 检查是否为工作日
            if not self.holiday_manager.is_workday(current_date):
                conflicts['workday_conflicts'].append(current_date.isoformat())
            
            # 检查周末
            if current_date.weekday() >= 5:
                conflicts['weekend_conflicts'].append(current_date.isoformat())
            
            # 检查调休
            for holiday in self.holiday_manager.get_holidays_by_year(current_date.year):
                if current_date in holiday.adjustment_days:
                    conflicts['adjustment_conflicts'].append({
                        'date': current_date.isoformat(),
                        'holiday': holiday.name
                    })
            
            # 检查个人请假冲突
            for record in profile.leave_history:
                if record.is_conflict(LeaveRecord(LeaveType.ANNUAL, current_date, current_date, 1)):
                    conflicts['personal_conflicts'].append({
                        'date': current_date.isoformat(),
                        'type': record.leave_type.value,
                        'reason': record.reason
                    })
            
            current_date += timedelta(days=1)

        return conflicts

    def suggest_optimal_dates(self, desired_start: date, duration: int, 
                             profile: PersonalLeaveProfile) -> Dict:
        """建议最优日期"""
        # 检查当前日期是否有冲突
        conflicts = self.detect_conflicts(desired_start, 
                                        desired_start + timedelta(days=duration - 1), 
                                        profile)
        
        has_conflict = any(len(v) > 0 for v in conflicts.values())
        
        if not has_conflict:
            return {
                'original_dates': {
                    'start': desired_start.isoformat(),
                    'end': (desired_start + timedelta(days=duration - 1)).isoformat()
                },
                'status': 'optimal',
                'conflicts': conflicts
            }
        
        # 寻找替代日期
        alternatives = []
        
        # 向前搜索
        for offset in range(1, 8):
            new_start = desired_start - timedelta(days=offset)
            new_conflicts = self.detect_conflicts(new_start, 
                                                new_start + timedelta(days=duration - 1), 
                                                profile)
            if all(len(v) == 0 for v in new_conflicts.values()):
                alternatives.append({
                    'start': new_start.isoformat(),
                    'end': (new_start + timedelta(days=duration - 1)).isoformat(),
                    'direction': 'backward',
                    'days_offset': offset
                })
                break
        
        # 向后搜索
        for offset in range(1, 8):
            new_start = desired_start + timedelta(days=offset)
            new_conflicts = self.detect_conflicts(new_start, 
                                                new_start + timedelta(days=duration - 1), 
                                                profile)
            if all(len(v) == 0 for v in new_conflicts.values()):
                alternatives.append({
                    'start': new_start.isoformat(),
                    'end': (new_start + timedelta(days=duration - 1)).isoformat(),
                    'direction': 'forward',
                    'days_offset': offset
                })
                break
        
        return {
            'original_dates': {
                'start': desired_start.isoformat(),
                'end': (desired_start + timedelta(days=duration - 1)).isoformat()
            },
            'status': 'conflict',
            'conflicts': conflicts,
            'alternatives': alternatives
        }

一站式集成平台

Web API 接口设计

使用 Flask 创建一个简单的 Web API:

from flask import Flask, request, jsonify
from datetime import datetime
import json

app = Flask(__name__)

# 初始化服务
holiday_service = HolidayQueryService()
optimizer = VacationOptimizer(holiday_manager=holiday_service.manager)
conflict_detector = ConflictDetector(holiday_manager=holiday_service.manager)

# 存储用户数据(实际应用中应使用数据库)
user_profiles = {}

@app.route('/api/holidays/upcoming', methods=['GET'])
def get_upcoming_holidays():
    """获取即将到来的节假日"""
    count = request.args.get('count', 3, type=int)
    holidays = holiday_service.query_upcoming_holidays(count)
    return jsonify(holidays)

@app.route('/api/holidays/calendar', methods=['GET'])
def get_holiday_calendar():
    """获取节假日日历"""
    year = request.args.get('year', type=int)
    month = request.args.get('month', type=int)
    
    if not year:
        return jsonify({'error': 'year parameter is required'}), 400
    
    calendar = holiday_service.query_holiday_calendar(year, month)
    return jsonify(calendar)

@app.route('/api/workday/status', methods=['GET'])
def get_workday_status():
    """查询工作状态"""
    date_str = request.args.get('date')
    if not date_str:
        return jsonify({'error': 'date parameter is required'}), 400
    
    status = holiday_service.query_workday_status(date_str)
    return jsonify(status)

@app.route('/api/vacation/recommend', methods=['POST'])
def recommend_vacation():
    """推荐假期时间"""
    data = request.get_json()
    
    user_id = data.get('user_id')
    year = data.get('year')
    duration = data.get('duration')
    
    if not all([user_id, year, duration]):
        return jsonify({'error': 'Missing required parameters'}), 400
    
    # 获取或创建用户档案
    if user_id not in user_profiles:
        user_profiles[user_id] = PersonalLeaveProfile(user_id)
    
    profile = user_profiles[user_id]
    recommendations = optimizer.recommend_vacation_slots(profile, year, duration)
    
    return jsonify(recommendations)

@app.route('/api/vacation/check-conflicts', methods=['POST'])
def check_conflicts():
    """检查假期冲突"""
    data = request.get_json()
    
    user_id = data.get('user_id')
    start_date_str = data.get('start_date')
    duration = data.get('duration')
    
    if not all([user_id, start_date_str, duration]):
        return jsonify({'error': 'Missing required parameters'}), 400
    
    try:
        start_date = datetime.strptime(start_date_str, "%Y-%m-%d").date()
    except ValueError:
        return jsonify({'error': 'Invalid date format'}), 400
    
    # 获取用户档案
    profile = user_profiles.get(user_id, PersonalLeaveProfile(user_id))
    
    # 检查冲突
    result = conflict_detector.suggest_optimal_dates(start_date, duration, profile)
    
    return jsonify(result)

@app.route('/api/user/profile', methods=['GET', 'POST'])
def manage_user_profile():
    """管理用户档案"""
    user_id = request.args.get('user_id')
    
    if request.method == 'POST':
        data = request.get_json()
        if user_id not in user_profiles:
            user_profiles[user_id] = PersonalLeaveProfile(user_id)
        
        profile = user_profiles[user_id]
        
        # 更新偏好
        if 'preferred_months' in data:
            profile.preferred_travel_months = set(data['preferred_months'])
        if 'avoid_months' in data:
            profile.avoid_months = set(data['avoid_months'])
        if 'annual_total' in data:
            profile.annual_leave_total = data['annual_total']
            profile.annual_leave_remaining = data['annual_total']
        
        return jsonify({'status': 'updated'})
    
    # GET请求
    if not user_id:
        return jsonify({'error': 'user_id parameter is required'}), 400
    
    profile = user_profiles.get(user_id)
    if not profile:
        return jsonify({'error': 'User profile not found'}), 404
    
    return jsonify({
        'user_id': profile.user_id,
        'annual_leave_total': profile.annual_leave_total,
        'annual_leave_remaining': profile.annual_leave_remaining,
        'preferred_travel_months': list(profile.preferred_travel_months),
        'avoid_months': list(profile.avoid_months),
        'leave_history': [
            {
                'type': r.leave_type.value,
                'start': r.start_date.isoformat(),
                'end': r.end_date.isoformat(),
                'days': r.days,
                'reason': r.reason,
                'status': r.status
            } for r in profile.leave_history
        ]
    })

@app.route('/api/user/leave', methods=['POST'])
def add_leave_record():
    """添加请假记录"""
    data = request.get_json()
    
    user_id = data.get('user_id')
    leave_type = data.get('leave_type')
    start_date_str = data.get('start_date')
    end_date_str = data.get('end_date')
    reason = data.get('reason', '')
    
    if not all([user_id, leave_type, start_date_str, end_date_str]):
        return jsonify({'error': 'Missing required parameters'}), 400
    
    try:
        start_date = datetime.strptime(start_date_str, "%Y-%m-%d").date()
        end_date = datetime.strptime(end_date_str, "%Y-%m-%d").date()
    except ValueError:
        return jsonify({'error': 'Invalid date format'}), 400
    
    if start_date > end_date:
        return jsonify({'error': 'Start date must be before end date'}), 400
    
    days = (end_date - start_date).days + 1
    
    # 获取或创建用户档案
    if user_id not in user_profiles:
        user_profiles[user_id] = PersonalLeaveProfile(user_id)
    
    profile = user_profiles[user_id]
    
    # 创建请假记录
    record = LeaveRecord(
        leave_type=LeaveType(leave_type),
        start_date=start_date,
        end_date=end_date,
        days=days,
        reason=reason
    )
    
    # 添加记录
    if profile.add_leave_record(record):
        return jsonify({'status': 'added', 'days_remaining': profile.annual_leave_remaining})
    else:
        return jsonify({'error': 'Conflict or insufficient leave days'}), 400

if __name__ == '__main__':
    app.run(debug=True, port=5000)

前端界面示例(HTML + JavaScript)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能假期规划系统</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
        }
        .container {
            background: white;
            border-radius: 15px;
            padding: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }
        .section {
            margin-bottom: 25px;
            padding: 20px;
            border: 1px solid #e0e0e0;
            border-radius: 10px;
            background: #f9f9f9;
        }
        .section h2 {
            color: #555;
            margin-top: 0;
            border-bottom: 2px solid #667eea;
            padding-bottom: 10px;
        }
        .input-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #555;
        }
        input, select, button {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            font-size: 14px;
            box-sizing: border-box;
        }
        button {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            cursor: pointer;
            font-weight: bold;
            transition: transform 0.2s;
        }
        button:hover {
            transform: translateY(-2px);
        }
        .result {
            margin-top: 15px;
            padding: 15px;
            background: white;
            border-radius: 5px;
            border-left: 4px solid #667eea;
            white-space: pre-wrap;
            font-family: monospace;
            max-height: 300px;
            overflow-y: auto;
        }
        .grid-2 {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
        }
        .grid-3 {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 15px;
        }
        .status-badge {
            display: inline-block;
            padding: 3px 8px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: bold;
            margin-left: 5px;
        }
        .status-workday { background: #e8f5e9; color: #2e7d32; }
        .status-holiday { background: #fff3e0; color: #ef6c00; }
        .status-weekend { background: #e3f2fd; color: #1565c0; }
        .recommendation-card {
            background: white;
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            border-left: 4px solid #4caf50;
            cursor: pointer;
            transition: all 0.2s;
        }
        .recommendation-card:hover {
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            transform: translateX(5px);
        }
        .score {
            font-size: 24px;
            font-weight: bold;
            color: #4caf50;
        }
        .conflict-warning {
            background: #ffebee;
            border-left: 4px solid #f44336;
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .conflict-success {
            background: #e8f5e9;
            border-left: 4px solid #4caf50;
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
        }
        .loading {
            text-align: center;
            color: #667eea;
            font-weight: bold;
        }
        .error {
            background: #ffebee;
            color: #c62828;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
        }
        .success {
            background: #e8f5e9;
            color: #2e7d32;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
        }
        .tabs {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }
        .tab {
            padding: 10px 20px;
            background: #e0e0e0;
            border: none;
            border-radius: 5px 5px 0 0;
            cursor: pointer;
            font-weight: bold;
        }
        .tab.active {
            background: #667eea;
            color: white;
        }
        .tab-content {
            display: none;
        }
        .tab-content.active {
            display: block;
        }
        .multi-select {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            margin-top: 5px;
        }
        .checkbox-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        .checkbox-group input {
            width: auto;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🏖️ 智能假期规划系统</h1>
        
        <div class="tabs">
            <button class="tab active" onclick="switchTab('holidays')">节假日查询</button>
            <button class="tab" onclick="switchTab('recommend')">智能推荐</button>
            <button class="tab" onclick="switchTab('conflict')">冲突检测</button>
            <button class="tab" onclick="switchTab('profile')">个人档案</button>
        </div>

        <!-- 节假日查询 -->
        <div id="holidays" class="tab-content active">
            <div class="section">
                <h2>📅 即将到来的节假日</h2>
                <div class="input-group">
                    <label>显示数量:</label>
                    <input type="number" id="upcomingCount" value="5" min="1" max="10">
                </div>
                <button onclick="getUpcomingHolidays()">查询</button>
                <div id="upcomingResult" class="result"></div>
            </div>

            <div class="section">
                <h2>📆 节假日日历</h2>
                <div class="grid-2">
                    <div class="input-group">
                        <label>年份:</label>
                        <input type="number" id="calendarYear" value="2024">
                    </div>
                    <div class="input-group">
                        <label>月份(可选):</label>
                        <input type="number" id="calendarMonth" placeholder="1-12" min="1" max="12">
                    </div>
                </div>
                <button onclick="getHolidayCalendar()">查询日历</button>
                <div id="calendarResult" class="result"></div>
            </div>

            <div class="section">
                <h2>🔍 工作状态查询</h2>
                <div class="input-group">
                    <label>日期(YYYY-MM-DD):</label>
                    <input type="text" id="workdayDate" placeholder="2024-10-1" value="2024-10-1">
                </div>
                <button onclick="getWorkdayStatus()">查询状态</button>
                <div id="workdayResult" class="result"></div>
            </div>
        </div>

        <!-- 智能推荐 -->
        <div id="recommend" class="tab-content">
            <div class="section">
                <h2>🎯 最佳假期推荐</h2>
                <div class="grid-2">
                    <div class="input-group">
                        <label>用户ID:</label>
                        <input type="text" id="recommendUserId" placeholder="user123">
                    </div>
                    <div class="input-group">
                        <label>年份:</label>
                        <input type="number" id="recommendYear" value="2024">
                    </div>
                </div>
                <div class="input-group">
                    <label>假期天数:</label>
                    <input type="number" id="recommendDuration" value="5" min="1" max="30">
                </div>
                <button onclick="getRecommendations()">获取推荐</button>
                <div id="recommendResult" class="result"></div>
            </div>
        </div>

        <!-- 冲突检测 -->
        <div id="conflict" class="tab-content">
            <div class="section">
                <h2>⚠️ 冲突检测与优化</h2>
                <div class="grid-2">
                    <div class="input-group">
                        <label>用户ID:</label>
                        <input type="text" id="conflictUserId" placeholder="user123">
                    </div>
                    <div class="input-group">
                        <label>假期天数:</label>
                        <input type="number" id="conflictDuration" value="5" min="1">
                    </div>
                </div>
                <div class="input-group">
                    <label>期望开始日期(YYYY-MM-DD):</label>
                    <input type="text" id="conflictStartDate" placeholder="2024-10-1" value="2024-10-1">
                </div>
                <button onclick="checkConflicts()">检测冲突</button>
                <div id="conflictResult" class="result"></div>
            </div>
        </div>

        <!-- 个人档案 -->
        <div id="profile" class="tab-content">
            <div class="section">
                <h2>👤 个人档案管理</h2>
                <div class="input-group">
                    <label>用户ID:</label>
                    <input type="text" id="profileUserId" placeholder="user123">
                </div>
                <div class="grid-2">
                    <div class="input-group">
                        <label>年假总额:</label>
                        <input type="number" id="profileAnnualTotal" value="15">
                    </div>
                    <div class="input-group">
                        <label>操作:</label>
                        <button onclick="updateProfile()">更新档案</button>
                    </div>
                </div>
                
                <div class="input-group">
                    <label>偏好月份(可多选):</label>
                    <div class="multi-select" id="preferredMonths"></div>
                </div>
                
                <div class="input-group">
                    <label>避免月份(可多选):</label>
                    <div class="multi-select" id="avoidMonths"></div>
                </div>
                
                <div id="profileResult" class="result"></div>
            </div>

            <div class="section">
                <h2>📝 添加请假记录</h2>
                <div class="grid-2">
                    <div class="input-group">
                        <label>用户ID:</label>
                        <input type="text" id="leaveUserId" placeholder="user123">
                    </div>
                    <div class="input-group">
                        <label>假期类型:</label>
                        <select id="leaveType">
                            <option value="ANNUAL">年假</option>
                            <option value="SICK">病假</option>
                            <option value="PERSONAL">事假</option>
                            <option value="OTHER">其他</option>
                        </select>
                    </div>
                </div>
                <div class="grid-2">
                    <div class="input-group">
                        <label>开始日期:</label>
                        <input type="text" id="leaveStart" placeholder="2024-08-01">
                    </div>
                    <div class="input-group">
                        <label>结束日期:</label>
                        <input type="text" id="leaveEnd" placeholder="2024-08-05">
                    </div>
                </div>
                <div class="input-group">
                    <label>原因(可选):</label>
                    <input type="text" id="leaveReason" placeholder="家庭旅行">
                </div>
                <button onclick="addLeaveRecord()">添加记录</button>
                <div id="leaveResult" class="result"></div>
            </div>

            <div class="section">
                <h2>📊 假期统计</h2>
                <button onclick="getProfileStats()">查看统计</button>
                <div id="statsResult" class="result"></div>
            </div>
        </div>
    </div>

    <script>
        // API基础URL
        const API_BASE = 'http://localhost:5000/api';

        // 初始化月份选择器
        function initMonthSelectors() {
            const preferredContainer = document.getElementById('preferredMonths');
            const avoidContainer = document.getElementById('avoidMonths');
            
            const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
            
            months.forEach((month, index) => {
                const monthNum = index + 1;
                
                // 偏好月份
                const prefDiv = document.createElement('div');
                prefDiv.className = 'checkbox-group';
                prefDiv.innerHTML = `
                    <input type="checkbox" id="pref_${monthNum}" value="${monthNum}">
                    <label for="pref_${monthNum}">${month}</label>
                `;
                preferredContainer.appendChild(prefDiv);
                
                // 避免月份
                const avoidDiv = document.createElement('div');
                avoidDiv.className = 'checkbox-group';
                avoidDiv.innerHTML = `
                    <input type="checkbox" id="avoid_${monthNum}" value="${monthNum}">
                    <label for="avoid_${monthNum}">${month}</label>
                `;
                avoidContainer.appendChild(avoidDiv);
            });
        }

        // 切换标签页
        function switchTab(tabName) {
            // 隐藏所有内容
            document.querySelectorAll('.tab-content').forEach(content => {
                content.classList.remove('active');
            });
            
            // 移除所有标签激活状态
            document.querySelectorAll('.tab').forEach(tab => {
                tab.classList.remove('active');
            });
            
            // 显示选中内容
            document.getElementById(tabName).classList.add('active');
            event.target.classList.add('active');
        }

        // 显示结果
        function showResult(elementId, data, isError = false) {
            const element = document.getElementById(elementId);
            if (isError) {
                element.innerHTML = `<div class="error">${data}</div>`;
            } else if (typeof data === 'string') {
                element.innerHTML = `<div class="success">${data}</div>`;
            } else {
                element.innerHTML = `<div class="result">${JSON.stringify(data, null, 2)}</div>`;
            }
        }

        // 获取即将到来的节假日
        async function getUpcomingHolidays() {
            const count = document.getElementById('upcomingCount').value;
            try {
                const response = await fetch(`${API_BASE}/holidays/upcoming?count=${count}`);
                const data = await response.json();
                showResult('upcomingResult', data);
            } catch (error) {
                showResult('upcomingResult', `请求失败: ${error.message}`, true);
            }
        }

        // 获取节假日日历
        async function getHolidayCalendar() {
            const year = document.getElementById('calendarYear').value;
            const month = document.getElementById('calendarMonth').value;
            
            let url = `${API_BASE}/holidays/calendar?year=${year}`;
            if (month) url += `&month=${month}`;
            
            try {
                const response = await fetch(url);
                const data = await response.json();
                showResult('calendarResult', data);
            } catch (error) {
                showResult('calendarResult', `请求失败: ${error.message}`, true);
            }
        }

        // 查询工作状态
        async function getWorkdayStatus() {
            const date = document.getElementById('workdayDate').value;
            try {
                const response = await fetch(`${API_BASE}/workday/status?date=${date}`);
                const data = await response.json();
                
                if (data.error) {
                    showResult('workdayResult', data.error, true);
                } else {
                    const statusClass = data.is_workday ? 'status-workday' : (data.is_holiday ? 'status-holiday' : 'status-weekend');
                    const html = `
                        <div class="success">
                            <strong>日期:</strong> ${data.date}<br>
                            <strong>状态:</strong> <span class="status-badge ${statusClass}">${data.status}</span><br>
                            <strong>星期:</strong> ${data.weekday}
                        </div>
                    `;
                    document.getElementById('workdayResult').innerHTML = html;
                }
            } catch (error) {
                showResult('workdayResult', `请求失败: ${error.message}`, true);
            }
        }

        // 获取推荐
        async function getRecommendations() {
            const userId = document.getElementById('recommendUserId').value;
            const year = document.getElementById('recommendYear').value;
            const duration = document.getElementById('recommendDuration').value;
            
            if (!userId) {
                showResult('recommendResult', '请输入用户ID', true);
                return;
            }
            
            try {
                const response = await fetch(`${API_BASE}/vacation/recommend`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ user_id: userId, year: parseInt(year), duration: parseInt(duration) })
                });
                const data = await response.json();
                
                if (data.length === 0) {
                    showResult('recommendResult', '没有找到合适的推荐时间段');
                    return;
                }
                
                let html = '<div class="success">推荐结果(按优先级排序):</div>';
                data.forEach((rec, index) => {
                    html += `
                        <div class="recommendation-card" onclick="selectRecommendation('${rec.start_date}', ${rec.duration})">
                            <div style="display: flex; justify-content: space-between; align-items: center;">
                                <div>
                                    <strong>推荐 ${index + 1}:</strong> ${rec.start_date} 至 ${rec.end_date}<br>
                                    <small>持续 ${rec.duration} 天</small>
                                </div>
                                <div class="score">${rec.score}</div>
                            </div>
                            <div style="margin-top: 8px; font-size: 12px; color: #666;">
                                节假日: ${rec.details.holiday_score} | 偏好: ${rec.details.personal_score} | 天气: ${rec.details.weather_score} | 人流: ${rec.details.crowd_score} | 成本: ${rec.details.cost_score}
                            </div>
                        </div>
                    `;
                });
                document.getElementById('recommendResult').innerHTML = html;
            } catch (error) {
                showResult('recommendResult', `请求失败: ${error.message}`, true);
            }
        }

        // 选择推荐
        function selectRecommendation(date, duration) {
            document.getElementById('conflictStartDate').value = date;
            document.getElementById('conflictDuration').value = duration;
            switchTab('conflict');
            document.querySelectorAll('.tab')[2].click();
            checkConflicts();
        }

        // 检查冲突
        async function checkConflicts() {
            const userId = document.getElementById('conflictUserId').value;
            const startDate = document.getElementById('conflictStartDate').value;
            const duration = document.getElementById('conflictDuration').value;
            
            if (!userId || !startDate) {
                showResult('conflictResult', '请输入用户ID和开始日期', true);
                return;
            }
            
            try {
                const response = await fetch(`${API_BASE}/vacation/check-conflicts`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ 
                        user_id: userId, 
                        start_date: startDate, 
                        duration: parseInt(duration) 
                    })
                });
                const data = await response.json();
                
                let html = '';
                
                if (data.status === 'optimal') {
                    html += '<div class="conflict-success">✅ 完美!没有检测到任何冲突,这个时间段非常适合!</div>';
                } else {
                    html += '<div class="conflict-warning">⚠️ 检测到以下冲突:</div>';
                    
                    // 显示冲突详情
                    const conflicts = data.conflicts;
                    if (conflicts.holiday_conflicts.length > 0) {
                        html += `<strong>节假日冲突:</strong> ${conflicts.holiday_conflicts.join(', ')}<br>`;
                    }
                    if (conflicts.workday_conflicts.length > 0) {
                        html += `<strong>非工作日:</strong> ${conflicts.workday_conflicts.join(', ')}<br>`;
                    }
                    if (conflicts.personal_conflicts.length > 0) {
                        html += `<strong>个人请假冲突:</strong> ${JSON.stringify(conflicts.personal_conflicts)}<br>`;
                    }
                    if (conflicts.weekend_conflicts.length > 0) {
                        html += `<strong>周末:</strong> ${conflicts.weekend_conflicts.join(', ')}<br>`;
                    }
                    if (conflicts.adjustment_conflicts.length > 0) {
                        html += `<strong>调休工作日:</strong> ${JSON.stringify(conflicts.adjustment_conflicts)}<br>`;
                    }
                    
                    // 显示替代方案
                    if (data.alternatives && data.alternatives.length > 0) {
                        html += '<div style="margin-top: 15px;"><strong>替代方案:</strong></div>';
                        data.alternatives.forEach(alt => {
                            const direction = alt.direction === 'forward' ? '往后' : '往前';
                            html += `
                                <div class="recommendation-card" style="border-left-color: #ff9800;">
                                    ${alt.start} 至 ${alt.end} 
                                    <span style="color: #666;">(${direction} ${alt.days_offset} 天)</span>
                                </div>
                            `;
                        });
                    }
                }
                
                document.getElementById('conflictResult').innerHTML = html;
            } catch (error) {
                showResult('conflictResult', `请求失败: ${error.message}`, true);
            }
        }

        // 更新档案
        async function updateProfile() {
            const userId = document.getElementById('profileUserId').value;
            const annualTotal = document.getElementById('profileAnnualTotal').value;
            
            if (!userId) {
                showResult('profileResult', '请输入用户ID', true);
                return;
            }
            
            // 获取选中的月份
            const preferredMonths = [];
            const avoidMonths = [];
            
            for (let i = 1; i <= 12; i++) {
                if (document.getElementById(`pref_${i}`).checked) {
                    preferredMonths.push(i);
                }
                if (document.getElementById(`avoid_${i}`).checked) {
                    avoidMonths.push(i);
                }
            }
            
            try {
                const response = await fetch(`${API_BASE}/user/profile?user_id=${userId}`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        preferred_months: preferredMonths,
                        avoid_months: avoidMonths,
                        annual_total: parseInt(annualTotal)
                    })
                });
                const data = await response.json();
                showResult('profileResult', `✅ ${data.status || '档案已更新'}`);
            } catch (error) {
                showResult('profileResult', `请求失败: ${error.message}`, true);
            }
        }

        // 添加请假记录
        async function addLeaveRecord() {
            const userId = document.getElementById('leaveUserId').value;
            const leaveType = document.getElementById('leaveType').value;
            const startDate = document.getElementById('leaveStart').value;
            const endDate = document.getElementById('leaveEnd').value;
            const reason = document.getElementById('leaveReason').value;
            
            if (!userId || !startDate || !endDate) {
                showResult('leaveResult', '请填写必要信息', true);
                return;
            }
            
            try {
                const response = await fetch(`${API_BASE}/user/leave`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        user_id: userId,
                        leave_type: leaveType,
                        start_date: startDate,
                        end_date: endDate,
                        reason: reason
                    })
                });
                const data = await response.json();
                
                if (data.error) {
                    showResult('leaveResult', data.error, true);
                } else {
                    showResult('leaveResult', `✅ 请假记录已添加!剩余年假:${data.days_remaining} 天`);
                }
            } catch (error) {
                showResult('leaveResult', `请求失败: ${error.message}`, true);
            }
        }

        // 获取档案统计
        async function getProfileStats() {
            const userId = document.getElementById('profileUserId').value;
            
            if (!userId) {
                showResult('statsResult', '请输入用户ID', true);
                return;
            }
            
            try {
                const response = await fetch(`${API_BASE}/user/profile?user_id=${userId}`);
                const data = await response.json();
                
                if (data.error) {
                    showResult('statsResult', data.error, true);
                    return;
                }
                
                let html = `
                    <div class="success">
                        <strong>年假信息:</strong><br>
                        总额:${data.annual_leave_total} 天<br>
                        剩余:${data.annual_leave_remaining} 天<br>
                        已使用:${data.annual_leave_total - data.annual_leave_remaining} 天<br><br>
                        
                        <strong>偏好月份:</strong> ${data.preferred_travel_months.join(', ')}<br>
                        <strong>避免月份:</strong> ${data.avoid_months.join(', ')}<br><br>
                        
                        <strong>请假记录:</strong><br>
                `;
                
                if (data.leave_history.length === 0) {
                    html += '暂无请假记录';
                } else {
                    data.leave_history.forEach(record => {
                        html += `${record.start} 至 ${record.end} - ${record.type} (${record.days}天) - ${record.status}<br>`;
                    });
                }
                
                html += '</div>';
                document.getElementById('statsResult').innerHTML = html;
            } catch (error) {
                showResult('statsResult', `请求失败: ${error.message}`, true);
            }
        }

        // 页面加载时初始化
        window.onload = function() {
            initMonthSelectors();
        };
    </script>
</body>
</html>

高级功能扩展

1. 多年度假期规划

class MultiYearPlanner:
    """多年度假期规划器"""
    def __init__(self, holiday_manager: HolidayManager, optimizer: VacationOptimizer):
        self.holiday_manager = holiday_manager
        self.optimizer = optimizer

    def plan_multi_year_vacation(self, profile: PersonalLeaveProfile, 
                                 start_year: int, 
                                 end_year: int,
                                 annual_duration: int = 5) -> List[Dict]:
        """规划多年度假期"""
        plans = []
        
        for year in range(start_year, end_year + 1):
            # 检查该年是否有足够假期
            if profile.annual_leave_remaining <= 0:
                break
            
            # 推荐该年最佳时间
            recommendations = self.optimizer.recommend_vacation_slots(
                profile, year, annual_duration, max_results=3
            )
            
            if recommendations:
                plans.append({
                    'year': year,
                    'recommendations': recommendations,
                    'remaining_leave': profile.annual_leave_remaining
                })
        
        return plans

    def optimize_long_term_plan(self, profile: PersonalLeaveProfile, 
                                years: int,
                                min_annual_days: int = 5,
                                max_gap: int = 180) -> Dict:
        """优化长期规划,确保假期分布合理"""
        plans = []
        
        for year in range(date.today().year, date.today().year + years):
            # 推荐多个时间段
            all_recommendations = self.optimizer.recommend_vacation_slots(
                profile, year, min_annual_days, max_results=10
            )
            
            # 选择最佳的一个
            if all_recommendations:
                best = all_recommendations[0]
                plans.append({
                    'year': year,
                    'start_date': best['start_date'],
                    'end_date': best['end_date'],
                    'score': best['score']
                })
        
        # 计算间隔
        gaps = []
        for i in range(len(plans) - 1):
            start1 = datetime.strptime(plans[i]['start_date'], "%Y-%m-%d").date()
            start2 = datetime.strptime(plans[i+1]['start_date'], "%Y-%m-%d").date()
            gap = (start2 - start1).days
            gaps.append(gap)
        
        return {
            'plans': plans,
            'gaps': gaps,
            'avg_gap': statistics.mean(gaps) if gaps else 0,
            'recommendation': '间隔合理' if all(g <= max_gap for g in gaps) else '建议调整间隔'
        }

2. 天气集成(示例)

class WeatherService:
    """天气服务(模拟)"""
    def __init__(self):
        # 实际应用中应调用真实天气API
        self.weather_data = {
            1: {'temp': 5, 'condition': '寒冷', 'score': 3},
            2: {'temp': 8, 'condition': '寒冷', 'score': 3},
            3: {'temp': 15, 'condition': '温和', 'score': 8},
            4: {'temp': 20, 'condition': '宜人', 'score': 9},
            5: {'temp': 25, 'condition': '温暖', 'score': 8},
            6: {'temp': 28, 'condition': '炎热', 'score': 6},
            7: {'temp': 32, 'condition': '炎热', 'score': 5},
            8: {'temp': 31, 'condition': '炎热', 'score': 5},
            9: {'temp': 26, 'condition': '宜人', 'score': 9},
            10: {'temp': 20, 'condition': '宜人', 'score': 10},
            11: {'temp': 15, 'condition': '凉爽', 'score': 7},
            12: {'temp': 8, 'condition': '寒冷', 'score': 4}
        }

    def get_weather_score(self, date: date) -> float:
        """获取天气得分"""
        return self.weather_data.get(date.month, {}).get('score', 5)

    def get_travel_suggestion(self, month: int) -> str:
        """获取旅行建议"""
        if month in [3, 4, 5, 9, 10]:
            return "最佳旅行季节"
        elif month in [6, 7, 8]:
            return "适合避暑"
        elif month in [12, 1, 2]:
            return "适合冬季运动"
        else:
            return "一般"

3. 数据持久化

import sqlite3
import pickle

class DatabaseManager:
    """数据库管理器"""
    def __init__(self, db_path: str = "vacation_planner.db"):
        self.db_path = db_path
        self.init_database()

    def init_database(self):
        """初始化数据库"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 用户表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                user_id TEXT PRIMARY KEY,
                annual_total INTEGER,
                annual_remaining INTEGER,
                preferred_months TEXT,
                avoid_months TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        
        # 请假记录表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS leave_records (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id TEXT,
                leave_type TEXT,
                start_date TEXT,
                end_date TEXT,
                days INTEGER,
                reason TEXT,
                status TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users (user_id)
            )
        ''')
        
        # 节假日表(用于缓存)
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS holidays (
                year INTEGER,
                name TEXT,
                date TEXT,
                duration INTEGER,
                adjustment_days TEXT,
                PRIMARY KEY (year, name)
            )
        ''')
        
        conn.commit()
        conn.close()

    def save_user_profile(self, profile: PersonalLeaveProfile):
        """保存用户档案"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            INSERT OR REPLACE INTO users 
            (user_id, annual_total, annual_remaining, preferred_months, avoid_months)
            VALUES (?, ?, ?, ?, ?)
        ''', (
            profile.user_id,
            profile.annual_leave_total,
            profile.annual_leave_remaining,
            ','.join(map(str, profile.preferred_travel_months)),
            ','.join(map(str, profile.avoid_months))
        ))
        
        conn.commit()
        conn.close()

    def load_user_profile(self, user_id: str) -> Optional[PersonalLeaveProfile]:
        """加载用户档案"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('SELECT * FROM users WHERE user_id = ?', (user_id,))
        row = cursor.fetchone()
        
        if not row:
            conn.close()
            return None
        
        # 加载请假记录
        cursor.execute('SELECT * FROM leave_records WHERE user_id = ? AND status = "approved"', (user_id,))
        leave_rows = cursor.fetchall()
        
        conn.close()
        
        # 创建档案
        profile = PersonalLeaveProfile(
            user_id=row[0],
            annual_leave_total=row[1],
            annual_leave_remaining=row[2],
            preferred_travel_months=set(map(int, row[3].split(','))) if row[3] else set(),
            avoid_months=set(map(int, row[4].split(','))) if row[4] else set()
        )
        
        # 添加请假记录
        for leave_row in leave_rows:
            record = LeaveRecord(
                leave_type=LeaveType(leave_row[2]),
                start_date=datetime.strptime(leave_row[3], "%Y-%m-%d").date(),
                end_date=datetime.strptime(leave_row[4], "%Y-%m-%d").date(),
                days=leave_row[5],
                reason=leave_row[6],
                status=leave_row[7]
            )
            profile.leave_history.append(record)
        
        return profile

    def save_holidays(self, year: int, holidays: List[Holiday]):
        """保存节假日数据"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        for holiday in holidays:
            adjustment_days = ','.join([d.isoformat() for d in holiday.adjustment_days])
            cursor.execute('''
                INSERT OR REPLACE INTO holidays 
                (year, name, date, duration, adjustment_days)
                VALUES (?, ?, ?, ?, ?)
            ''', (year, holiday.name, holiday.date.isoformat(), holiday.duration, adjustment_days))
        
        conn.commit()
        conn.close()

    def load_holidays(self, year: int) -> List[Holiday]:
        """加载节假日数据"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('SELECT * FROM holidays WHERE year = ?', (year,))
        rows = cursor.fetchall()
        conn.close()
        
        holidays = []
        for row in rows:
            adjustment_days = []
            if row[4]:
                adjustment_days = [datetime.strptime(d, "%Y-%m-%d").date() for d in row[4].split(',')]
            
            holiday = Holiday(
                name=row[1],
                date=datetime.strptime(row[2], "%Y-%m-%d").date(),
                duration=row[3],
                adjustment_days=adjustment_days
            )
            holidays.append(holiday)
        
        return holidays

实际使用案例

案例1:上班族年度假期规划

def case_worker_annual_plan():
    """案例:上班族年度假期规划"""
    print("=== 案例1:上班族年度假期规划 ===")
    
    # 初始化服务
    holiday_manager = HolidayManager()
    optimizer = VacationOptimizer(holiday_manager)
    conflict_detector = ConflictDetector(holiday_manager)
    
    # 创建用户档案
    profile = PersonalLeaveProfile(
        user_id="worker001",
        annual_leave_total=15,
        annual_leave_remaining=15,
        preferred_travel_months={7, 8, 10},  # 喜欢暑假和国庆
        avoid_months={1, 2, 12}  # 避开年底和年初
    )
    
    # 规划2024年假期
    print("\n1. 推荐2024年5天假期:")
    recommendations = optimizer.recommend_vacation_slots(profile, 2024, 5, max_results=3)
    for i, rec in enumerate(recommendations, 1):
        print(f"  推荐{i}: {rec['start_date']} 至 {rec['end_date']} (得分: {rec['score']})")
    
    # 选择一个推荐并检查冲突
    chosen = recommendations[0]
    start_date = datetime.strptime(chosen['start_date'], "%Y-%m-%d").date()
    
    print(f"\n2. 检查 {chosen['start_date']} 的冲突:")
    conflict_result = conflict_detector.suggest_optimal_dates(start_date, 5, profile)
    
    if conflict_result['status'] == 'optimal':
        print("  ✅ 完美!没有冲突")
    else:
        print("  ⚠️ 发现冲突,建议替代方案:")
        for alt in conflict_result.get('alternatives', []):
            print(f"    - {alt['start']} 至 {alt['end']} ({alt['direction']} {alt['days_offset']}天)")
    
    # 添加已使用的假期
    print("\n3. 添加已使用的假期记录:")
    used_record = LeaveRecord(
        leave_type=LeaveType.ANNUAL,
        start_date=date(2024, 7, 15),
        end_date=date(2024, 7, 19),
        days=5,
        reason="家庭旅行"
    )
    profile.add_leave_record(used_record)
    print(f"  已添加:{used_record.start_date} 至 {used_record.end_date}")
    print(f"  剩余年假:{profile.annual_leave_remaining} 天")
    
    # 统计
    stats = profile.get_leave_stats()
    print(f"\n4. 假期使用统计:")
    print(f"  总使用:{stats['total_used']} 天")
    print(f"  按类型:{stats['by_type']}")
    print(f"  按月份:{stats['by_month']}")

# 运行案例
case_worker_annual_plan()

输出示例:

=== 案例1:上班族年度假期规划 ===

1. 推荐2024年5天假期:
  推荐1: 2024-09-29 至 2024-10-03 (得分: 18.5)
  推荐2: 2024-06-10 至 2024-06-14 (得分: 16.2)
  推荐3: 2024-04-28 至 2024-05-02 (得分: 15.8)

2. 检查 2024-09-29 的冲突:
  ✅ 完美!没有冲突

3. 添加已使用的假期记录:
  已添加:2024-07-15 至 2024-07-19
  剩余年假:10 天

4. 假期使用统计:
  总使用:5 天
  按类型:{'年假': 5}
  按月份:{7: 5}

案例2:教师假期优化

def case_teacher_plan():
    """案例:教师假期规划(避开寒暑假)"""
    print("\n=== 案例2:教师假期规划 ===")
    
    holiday_manager = HolidayManager()
    optimizer = VacationOptimizer(holiday_manager)
    
    # 教师档案:避开7-8月(暑假)和1-2月(寒假)
    profile = PersonalLeaveProfile(
        user_id="teacher001",
        annual_leave_total=20,
        annual_leave_remaining=20,
        preferred_travel_months={4, 5, 9, 10},  # 春秋季节
        avoid_months={1, 2, 7, 8}  # 避开寒暑假
    )
    
    # 推荐2024年7天假期
    print("推荐2024年7天假期(避开寒暑假):")
    recommendations = optimizer.recommend_vacation_slots(profile, 2024, 7, max_results=5)
    
    for i, rec in enumerate(recommendations, 1):
        start_date = datetime.strptime(rec['start_date'], "%Y-%m-%d").date()
        month = start_date.month
        
        # 检查是否在避免月份
        if month in profile.avoid_months:
            print(f"  {i}. {rec['start_date']} 至 {rec['end_date']} (得分: {rec['score']}) - ⚠️ 在避免月份")
        else:
            print(f"  {i}. {rec['start_date']} 至 {rec['end_date']} (得分: {rec['score']}) - ✅ 推荐")
    
    # 找到最佳推荐
    best = recommendations[0]
    print(f"\n最佳推荐:{best['start_date']} 至 {best['end_date']}")

# 运行案例
case_teacher_plan()

案例3:多年度规划

def case_multi_year_plan():
    """案例:多年度假期规划"""
    print("\n=== 案例3:多年度假期规划 ===")
    
    holiday_manager = HolidayManager()
    optimizer = VacationOptimizer(holiday_manager)
    multi_planner = MultiYearPlanner(holiday_manager, optimizer)
    
    # 创建用户档案
    profile = PersonalLeaveProfile(
        user_id="planner001",
        annual_leave_total=15,
        annual_leave_remaining=30  # 假设有两年未使用的假期
    )
    
    # 规划2024-2025年
    print("规划2024-2025年假期:")
    plans = multi_planner.plan_multi_year_vacation(profile, 2024, 2025, 5)
    
    for plan in plans:
        print(f"\n{plan['year']}年:")
        for i, rec in enumerate(plan['recommendations'], 1):
            print(f"  选项{i}: {rec['start_date']} 至 {rec['end_date']} (得分: {rec['score']})")
        print(f"  剩余假期: {plan['remaining_leave']} 天")
    
    # 优化长期规划
    print("\n优化长期规划(确保间隔合理):")
    long_term = multi_planner.optimize_long_term_plan(profile, 3, min_annual_days=5, max_gap=200)
    
    for plan in long_term['plans']:
        print(f"  {plan['year']}: {plan['start_date']} (得分: {plan['score']})")
    
    print(f"\n平均间隔: {long_term['avg_gap']:.0f} 天")
    print(f"建议: {long_term['recommendation']}")

# 运行案例
case_multi_year_plan()

系统部署与扩展建议

1. 生产环境部署

# 使用 Gunicorn 部署 Flask 应用
# gunicorn -w 4 -b 0.0.0.0:5000 app:app

# 使用 Docker 容器化
"""
Dockerfile:
FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .
EXPOSE 5000

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
"""

# docker-compose.yml
"""
version: '3'
services:
  vacation-planner:
    build: .
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=sqlite:///vacation.db
    volumes:
      - ./data:/app/data
"""

2. 性能优化建议

  1. 缓存策略:节假日数据相对固定,应使用 Redis 缓存
  2. 异步处理:推荐算法可使用 Celery 异步执行
  3. 数据库索引:为常用查询字段添加索引
  4. CDN加速:前端资源使用 CDN 分发

3. 安全考虑

# 添加认证中间件
from functools import wraps
from flask import request, jsonify
import jwt

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        if not token:
            return jsonify({'error': 'Token missing'}), 401
        
        try:
            data = jwt.decode(token, 'secret_key', algorithms=['HS256'])
            request.user_id = data['user_id']
        except:
            return jsonify({'error': 'Invalid token'}), 401
        
        return f(*args, **kwargs)
    return decorated

# 在路由中使用
@app.route('/api/user/profile')
@token_required
def get_profile():
    user_id = request.user_id
    # ... 获取用户数据

总结

本文详细介绍了一个完整的排期预测与节日放假时间查询一站式解决方案。该系统具有以下特点:

核心优势

  1. 智能化推荐:基于多维度评分算法,推荐最佳假期时间
  2. 冲突检测:自动识别节假日、调休、个人请假等冲突
  3. 一站式服务:查询、规划、管理一体化
  4. 个性化定制:支持用户偏好设置和历史数据分析

技术亮点

  • 模块化设计:各功能模块解耦,易于扩展和维护
  • 完整代码示例:提供可直接运行的 Python 代码和 Web API
  • 前端界面:包含完整的 HTML/JavaScript 前端示例
  • 数据持久化:支持 SQLite 数据库存储

适用场景

  • 个人假期规划
  • 家庭旅行安排
  • 企业员工福利管理
  • HR 系统集成

通过这个解决方案,用户可以轻松规划假期,避免冲突,最大化假期利用效率,真正实现”一站式”假期管理。


使用提示

  1. 首次使用时,建议先运行 HolidayManager 初始化节假日数据
  2. 创建个人档案后,系统会记住您的偏好,提供更精准的推荐
  3. 定期更新请假记录,保持假期余额准确
  4. 推荐功能会考虑您的个人偏好,建议设置准确的偏好月份