引言:理解排期预测与展览活动日期查询的重要性
在现代展览活动管理中,排期预测和日期查询是核心业务功能。展览活动通常涉及多个场地、不同类型的活动、复杂的资源分配以及多变的客户需求。一个高效的排期预测系统不仅能帮助活动组织者合理安排资源,还能为客户提供准确的日期查询服务,从而提升整体运营效率和客户满意度。
排期预测展览活动日期查询系统本质上是一个智能调度系统,它需要考虑多种约束条件,包括场地可用性、活动类型、季节性因素、历史数据趋势以及客户偏好等。通过科学的预测算法和高效的查询机制,系统能够为展览活动的规划和执行提供有力支持。
本文将详细介绍如何设计和实现一个完整的排期预测展览活动日期查询系统,包括系统架构设计、核心算法实现、数据库设计以及前端查询接口等关键内容。
系统架构设计
整体架构概述
一个完整的排期预测展览活动日期查询系统通常包含以下几个核心模块:
- 数据采集与存储模块:负责收集和存储展览活动相关的历史数据
- 预测分析模块:基于历史数据进行趋势分析和日期预测
- 排期优化模块:考虑多种约束条件进行最优排期安排
- 查询接口模块:提供用户友好的查询界面和API接口
- 可视化展示模块:以图表形式展示排期结果和预测趋势
技术栈选择
- 后端:Python(Flask/Django)或Node.js
- 数据库:PostgreSQL(支持地理空间数据)或MongoDB
- 预测算法:Prophet、ARIMA、LSTM神经网络
- 前端:React/Vue.js + ECharts/D3.js
- 缓存:Redis
- 消息队列:RabbitMQ/Kafka(用于异步处理预测任务)
数据库设计
核心数据表结构
展览活动排期系统需要设计合理的数据模型来存储活动信息、场地信息、历史排期记录等。
-- 场地信息表
CREATE TABLE venues (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
location VARCHAR(255),
capacity INT,
facilities TEXT[],
available_days INT[] -- 0-6表示周日到周六,1表示可用,0表示不可用
);
-- 活动类型表
CREATE TABLE event_types (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
typical_duration_days INT,
season_preference INT[] -- 偏好季节,如[3,4,5]表示春季
);
-- 展览活动表
CREATE TABLE exhibitions (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
event_type_id INT REFERENCES event_types(id),
venue_id INT REFERENCES venues(id),
start_date DATE,
end_date DATE,
status VARCHAR(50), -- 'planned', 'confirmed', 'completed', 'cancelled'
expected_attendees INT,
actual_attendees INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 历史排期记录表
CREATE TABLE schedule_history (
id SERIAL PRIMARY KEY,
exhibition_id INT REFERENCES exhibitions(id),
predicted_start_date DATE,
predicted_end_date DATE,
actual_start_date DATE,
actual_end_date DATE,
prediction_accuracy FLOAT,
model_version VARCHAR(50)
);
-- 客户查询记录表
CREATE TABLE customer_queries (
id SERIAL PRIMARY KEY,
customer_id INT,
preferred_venue_id INT REFERENCES venues(id),
event_type_id INT REFERENCES event_types(id),
preferred_date_range_start DATE,
preferred_date_range_end DATE,
query_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
result_exhibition_ids INT[]
);
数据关系说明
- venues表存储展览场地信息,包括位置、容量和每周可用日期
- event_types表定义不同类型的展览活动及其季节性偏好
- exhibitions表记录具体的展览活动安排,包括实际日期和状态
- schedule_history表保存预测与实际日期的对比数据,用于模型优化
- customer_queries表记录用户查询历史,用于分析用户偏好和优化推荐
核心算法实现
1. 基于历史数据的日期预测算法
展览活动日期预测需要考虑多种因素,包括季节性、场地约束、活动类型特征等。以下是使用Python和Prophet库实现的预测算法:
import pandas as pd
from prophet import Prophet
import numpy as np
from datetime import datetime, timedelta
from typing import List, Dict, Tuple
class ExhibitionDatePredictor:
def __init__(self):
self.models = {} # 按活动类型存储训练好的模型
self.historical_data = None
def load_historical_data(self, db_connection):
"""从数据库加载历史排期数据"""
query = """
SELECT
e.start_date as ds,
e.event_type_id,
et.name as event_type_name,
e.venue_id,
v.name as venue_name,
e.expected_attendees as y,
e.status
FROM exhibitions e
JOIN event_types et ON e.event_type_id = et.id
JOIN venues v ON e.venue_id = v.id
WHERE e.status = 'completed'
ORDER BY e.start_date
"""
self.historical_data = pd.read_sql(query, db_connection)
return self.historical_data
def train_models(self):
"""为每种活动类型训练预测模型"""
if self.historical_data is None:
raise ValueError("Historical data not loaded")
for event_type_id in self.historical_data['event_type_id'].unique():
event_data = self.historical_data[
self.historical_data['event_type_id'] == event_type_id
].copy()
# Prophet要求列名为ds和y
prophet_data = event_data[['start_date', 'expected_attendees']].rename(
columns={'start_date': 'ds', 'expected_attendees': 'y'}
)
# 初始化模型,添加季节性因素
model = Prophet(
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False,
seasonality_mode='multiplicative'
)
# 添加场地约束作为额外回归因子
# 这里简化处理,实际中可以添加更多特征
model.add_country_holidays(country_name='CN')
# 训练模型
model.fit(prophet_data)
self.models[event_type_id] = model
def predict_available_dates(self, event_type_id: int, venue_id: int,
start_date: datetime, end_date: datetime,
min_duration: int = 1) -> List[Dict]:
"""
预测指定活动类型在指定场地的可用日期范围
Args:
event_type_id: 活动类型ID
venue_id: 场地ID
start_date: 查询开始日期
end_date: 查询结束日期
min_duration: 最小持续天数
Returns:
可用日期范围列表
"""
if event_type_id not in self.models:
raise ValueError(f"No model trained for event_type_id {event_type_id}")
model = self.models[event_type_id]
# 生成未来日期的DataFrame
future_dates = model.make_future_dataframe(
periods=(end_date - start_date).days + 1,
freq='D'
)
# 预测每个日期的活动热度(attendees)
forecast = model.predict(future_dates)
# 获取场地可用性约束
venue_availability = self._get_venue_availability(venue_id)
# 筛选可用日期
available_slots = []
current_slot = None
for idx, row in forecast.iterrows():
date = row['ds']
# 检查日期是否在查询范围内
if date < start_date or date > end_date:
continue
# 检查场地是否可用(周几)
weekday = date.weekday() # 0=Monday, 6=Sunday
if not venue_availability[weekday]:
# 如果当前有正在构建的时段,需要结束它
if current_slot:
available_slots.append(current_slot)
current_slot = None
continue
# 检查活动热度是否过高(可选的业务规则)
# 这里假设热度超过某个阈值就不适合安排
if row['yhat'] > 50000: # 假设阈值
if current_slot:
available_slots.append(current_slot)
current_slot = None
continue
# 构建连续可用时段
if current_slot is None:
current_slot = {
'start_date': date,
'end_date': date,
'predicted_attendees': row['yhat'],
'confidence': 1 - row['yhat_lower'] / row['yhat_upper']
}
else:
# 检查是否连续
if date == current_slot['end_date'] + timedelta(days=1):
current_slot['end_date'] = date
current_slot['predicted_attendees'] = (
current_slot['predicted_attendees'] + row['yhat']
) / 2 # 平均热度
else:
# 不连续,结束当前时段
if (current_slot['end_date'] - current_slot['start_date']).days + 1 >= min_duration:
available_slots.append(current_slot)
current_slot = {
'start_date': date,
'end_date': date,
'predicted_attendees': row['yhat'],
'confidence': 1 - row['yhat_lower'] / row['yhat_upper']
}
# 添加最后一个时段
if current_slot and (current_slot['end_date'] - current_slot['start_date']).days + 1 >= min_duration:
available_slots.append(current_slot)
return available_slots
def _get_venue_availability(self, venue_id: int) -> List[bool]:
"""获取场地的周几可用性(0=周一到6=周日)"""
# 这里应该从数据库查询,简化为示例
# 假设大多数场地周一到周日都可用,但有些场地可能周末不可用
if venue_id % 2 == 0:
# 场地A:周一到周五可用
return [True, True, True, True, True, False, False]
else:
# 场地B:每天都可用
return [True, True, True, True, True, True, True]
def evaluate_prediction_accuracy(self, event_type_id: int) -> float:
"""评估模型预测准确率"""
if event_type_id not in self.models:
return 0.0
# 获取该活动类型的历史数据
historical = self.historical_data[
self.historical_data['event_type_id'] == event_type_id
]
if len(historical) < 5:
return 0.0
# 使用交叉验证评估
# 这里简化处理,实际应该使用更严格的验证方法
from sklearn.metrics import mean_absolute_error
predictions = []
actuals = []
for idx, row in historical.iterrows():
# 预测该日期的活动热度
ds = pd.DataFrame({'ds': [row['start_date']]})
forecast = self.models[event_type_id].predict(ds)
predictions.append(forecast['yhat'].values[0])
actuals.append(row['expected_attendees'])
mae = mean_absolute_error(actuals, predictions)
accuracy = 1 - (mae / np.mean(actuals))
return max(0, accuracy) # 确保不为负数
# 使用示例
if __name__ == "__main__":
# 初始化预测器
predictor = ExhibitionDatePredictor()
# 模拟数据加载(实际应从数据库)
# predictor.load_historical_data(db_connection)
# 训练模型
# predictor.train_models()
# 预测可用日期
# available_dates = predictor.predict_available_dates(
# event_type_id=1,
# venue_id=1,
# start_date=datetime(2024, 1, 1),
# end_date=datetime(2024, 12, 31),
# min_duration=3
# )
# print(available_dates)
2. 排期优化算法
在预测的基础上,还需要考虑多约束条件的排期优化:
from ortools.sat.python import cp_model
from typing import List, Dict
import datetime
class ScheduleOptimizer:
def __init__(self):
self.model = cp_model.CpModel()
self.solver = cp_model.CpSolver()
def optimize_schedule(self, requests: List[Dict], venues: List[Dict]) -> Dict:
"""
优化排期,最大化场地利用率和客户满意度
Args:
requests: 客户请求列表,包含活动类型、期望日期、持续天数等
venues: 场地列表,包含容量、可用日期等
Returns:
优化后的排期结果
"""
# 创建变量
schedule_vars = {}
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 为每个请求-场地组合创建布尔变量
var_name = f"schedule_{i}_{j}"
schedule_vars[(i, j)] = self.model.NewBoolVar(var_name)
# 约束1:每个请求只能分配一个场地
for i, req in enumerate(requests):
self.model.Add(sum(schedule_vars[(i, j)] for j in range(len(venues))) == 1)
# 约束2:场地在时间上不能重叠(简化处理,实际需要更复杂的时间建模)
# 这里假设我们已经将时间离散化为时间段
for j, venue in enumerate(venues):
for time_slot in range(365): # 一年中的每一天
# 计算该时间段内有多少活动被安排
overlapping = sum(
schedule_vars[(i, j)]
for i, req in enumerate(requests)
if self._is_activity_in_slot(req, time_slot)
)
# 假设场地每天只能容纳一个活动
self.model.Add(overlapping <= 1)
# 约束3:场地容量约束
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
if req['expected_attendees'] > venue['capacity']:
# 如果活动预期人数超过场地容量,则不能分配
self.model.Add(schedule_vars[(i, j)] == 0)
# 约束4:场地可用日期约束
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 检查请求的日期是否在场地可用日期内
if not self._check_venue_availability(req, venue):
self.model.Add(schedule_vars[(i, j)] == 0)
# 目标函数:最大化总满意度(基于预测准确率和容量匹配度)
objective_terms = []
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 满意度评分:容量匹配度 + 预测准确率
capacity_score = 1 - abs(req['expected_attendees'] - venue['capacity']) / venue['capacity']
# 假设我们有预测准确率
prediction_score = req.get('prediction_accuracy', 0.8)
score = capacity_score * prediction_score
objective_terms.append(schedule_vars[(i, j)] * score * 100)
self.model.Maximize(sum(objective_terms))
# 求解
status = self.solver.Solve(self.model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
result = []
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
if self.solver.Value(schedule_vars[(i, j)]) == 1:
result.append({
'request_id': req['id'],
'venue_id': venue['id'],
'venue_name': venue['name'],
'scheduled_date': req['preferred_start_date'],
'satisfaction_score': self.solver.ObjectiveValue() / len(requests)
})
return {'status': 'success', 'schedule': result}
else:
return {'status': 'failed', 'message': 'No feasible schedule found'}
def _is_activity_in_slot(self, req: Dict, time_slot: int) -> bool:
"""检查活动是否在指定时间段内"""
start = req['preferred_start_date'].timetuple().tm_yday
duration = req.get('duration_days', 1)
return start <= time_slot < start + duration
def _check_venue_availability(self, req: Dict, venue: Dict) -> bool:
"""检查场地在请求日期是否可用"""
# 检查周几可用性
start_date = req['preferred_start_date']
weekday = start_date.weekday()
# venue['available_days'] 是一个长度为7的列表,表示周一到周日
if not venue['available_days'][weekday]:
return False
# 可以添加更多检查,如节假日、已有排期等
return True
# 使用示例
if __name__ == "__main__":
optimizer = ScheduleOptimizer()
# 示例请求
requests = [
{
'id': 1,
'event_type_id': 1,
'expected_attendees': 2000,
'preferred_start_date': datetime.date(2024, 3, 15),
'duration_days': 3,
'prediction_accuracy': 0.85
},
{
'id': 2,
'event_type_id': 2,
'expected_attendees': 5000,
'preferred_start_date': datetime.date(2024, 3, 20),
'duration_days': 5,
'prediction_accuracy': 0.92
}
]
venues = [
{
'id': 1,
'name': '主展厅',
'capacity': 3000,
'available_days': [True, True, True, True, True, True, True] # 周一到周日
},
{
'id': 2,
'name': '多功能厅',
'capacity': 6000,
'available_days': [True, True, True, True, True, False, False] # 周一到周五
}
]
# 优化排期
result = optimizer.optimize_schedule(requests, venues)
print(result)
查询接口设计
RESTful API设计
from flask import Flask, request, jsonify
from datetime import datetime, timedelta
import json
app = Flask(__name__)
# 初始化预测器和优化器
predictor = ExhibitionDatePredictor()
optimizer = ScheduleOptimizer()
@app.route('/api/v1/predict/available-dates', methods=['POST'])
def predict_available_dates():
"""
预测指定条件下的可用日期范围
请求体示例:
{
"event_type_id": 1,
"venue_id": 1,
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"min_duration": 3
}
"""
try:
data = request.get_json()
# 参数验证
required_fields = ['event_type_id', 'venue_id', 'start_date', 'end_date']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Missing required field: {field}'}), 400
# 转换日期
start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date()
end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date()
# 调用预测器
available_dates = predictor.predict_available_dates(
event_type_id=data['event_type_id'],
venue_id=data['venue_id'],
start_date=start_date,
end_date=end_date,
min_duration=data.get('min_duration', 1)
)
return jsonify({
'status': 'success',
'data': available_dates,
'count': len(available_dates)
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/schedule/optimize', methods=['POST'])
def optimize_schedule():
"""
优化排期
请求体示例:
{
"requests": [
{
"id": 1,
"event_type_id": 1,
"expected_attendees": 2000,
"preferred_start_date": "2024-03-15",
"duration_days": 3
}
],
"venues": [
{
"id": 1,
"name": "主展厅",
"capacity": 3000,
"available_days": [1,1,1,1,1,1,1]
}
]
}
"""
try:
data = request.get_json()
# 转换日期格式
for req in data['requests']:
req['preferred_start_date'] = datetime.strptime(
req['preferred_start_date'], '%Y-%m-%d'
).date()
# 调用优化器
result = optimizer.optimize_schedule(
data['requests'],
data['venues']
)
return jsonify(result)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/query/history', methods=['GET'])
def get_query_history():
"""
获取查询历史记录
支持分页和过滤
"""
try:
page = int(request.args.get('page', 1))
per_page = int(request.args.get('per_page', 20))
customer_id = request.args.get('customer_id')
# 从数据库查询(示例代码)
# 实际应该查询customer_queries表
history = [
{
'id': 1,
'customer_id': customer_id,
'query_timestamp': '2024-01-15T10:30:00',
'preferred_venue_id': 1,
'event_type_id': 1,
'preferred_date_range': '2024-03-01 to 2024-03-31',
'result_count': 5
}
]
return jsonify({
'status': 'success',
'data': history,
'pagination': {
'page': page,
'per_page': per_page,
'total': len(history)
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/venue/availability', methods=['GET'])
def get_venue_availability():
"""
查询场地在指定日期范围内的可用性
"""
try:
venue_id = request.args.get('venue_id')
start_date = datetime.strptime(request.args.get('start_date'), '%Y-%m-%d').date()
end_date = datetime.strptime(request.args.get('end_date'), '%Y-%m-%d').date()
# 这里应该查询数据库中的排期记录
# 示例:检查是否有已排期的活动
booked_dates = [] # 从数据库查询已排期日期
# 生成可用性日历
availability = []
current_date = start_date
while current_date <= end_date:
is_available = current_date not in booked_dates
availability.append({
'date': current_date.isoformat(),
'available': is_available,
'weekday': current_date.strftime('%A')
})
current_date += timedelta(days=1)
return jsonify({
'status': 'success',
'venue_id': venue_id,
'availability': availability
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
API使用示例
使用curl测试API:
# 预测可用日期
curl -X POST http://localhost:5000/api/v1/predict/available-dates \
-H "Content-Type: application/json" \
-d '{
"event_type_id": 1,
"venue_id": 1,
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"min_duration": 3
}'
# 优化排期
curl -X POST http://localhost:5000/api/v1/schedule/optimize \
-H "Content-Type: application/json" \
-d '{
"requests": [
{
"id": 1,
"event_type_id": 1,
"expected_attendees": 2000,
"preferred_start_date": "2024-03-15",
"duration_days": 3
}
],
"venues": [
{
"id": 1,
"name": "主展厅",
"capacity": 3000,
"available_days": [1,1,1,1,1,1,1]
}
]
}'
# 查询场地可用性
curl -X GET "http://localhost:5000/api/v1/venue/availability?venue_id=1&start_date=2024-03-01&end_date=2024-03-31"
前端查询界面设计
React组件示例
import React, { useState, useEffect } from 'react';
import { Calendar, Select, Button, Card, Table, message } from 'antd';
import axios from 'axios';
import moment from 'moment';
const { Option } = Select;
const ExhibitionScheduleQuery = () => {
const [venues, setVenues] = useState([]);
const [eventTypes, setEventTypes] = useState([]);
const [selectedVenue, setSelectedVenue] = useState(null);
const [selectedEventType, setSelectedEventType] = useState(null);
const [dateRange, setDateRange] = useState([moment(), moment().add(1, 'year')]);
const [availableDates, setAvailableDates] = useState([]);
const [loading, setLoading] = useState(false);
// 加载基础数据
useEffect(() => {
fetchVenues();
fetchEventTypes();
}, []);
const fetchVenues = async () => {
try {
const response = await axios.get('/api/v1/venues');
setVenues(response.data.data);
} catch (error) {
message.error('加载场地数据失败');
}
};
const fetchEventTypes = async () => {
try {
const response = await axios.get('/api/v1/event-types');
setEventTypes(response.data.data);
} catch (error) {
message.error('加载活动类型数据失败');
}
};
const handlePredict = async () => {
if (!selectedVenue || !selectedEventType) {
message.warning('请选择场地和活动类型');
return;
}
setLoading(true);
try {
const response = await axios.post('/api/v1/predict/available-dates', {
event_type_id: selectedEventType,
venue_id: selectedVenue,
start_date: dateRange[0].format('YYYY-MM-DD'),
end_date: dateRange[1].format('YYYY-MM-DD'),
min_duration: 3
});
setAvailableDates(response.data.data);
message.success(`找到 ${response.data.count} 个可用时段`);
} catch (error) {
message.error('预测失败:' + error.message);
} finally {
setLoading(false);
}
};
const columns = [
{
title: '开始日期',
dataIndex: 'start_date',
key: 'start_date',
render: (text) => moment(text).format('YYYY-MM-DD')
},
{
title: '结束日期',
dataIndex: 'end_date',
key: 'end_date',
render: (text) => moment(text).format('YYYY-MM-DD')
},
{
title: '持续天数',
key: 'duration',
render: (text, record) => {
const start = moment(record.start_date);
const end = moment(record.end_date);
return end.diff(start, 'days') + 1;
}
},
{
title: '预测人数',
dataIndex: 'predicted_attendees',
key: 'predicted_attendees'
},
{
title: '置信度',
dataIndex: 'confidence',
key: 'confidence',
render: (text) => `${(text * 100).toFixed(1)}%`
}
];
// 自定义日历单元格渲染
const dateCellRender = (value) => {
const dateStr = value.format('YYYY-MM-DD');
const slot = availableDates.find(slot => {
const start = moment(slot.start_date);
const end = moment(slot.end_date);
return value.isBetween(start, end, 'day', '[]');
});
if (slot) {
return (
<div style={{
background: '#52c41a',
color: 'white',
padding: '2px',
borderRadius: '4px',
fontSize: '10px'
}}>
可用
</div>
);
}
return null;
};
return (
<div style={{ padding: '20px' }}>
<Card title="展览活动排期预测查询" style={{ marginBottom: '20px' }}>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', marginBottom: '10px' }}>
<Select
style={{ width: 200 }}
placeholder="选择场地"
onChange={(value) => setSelectedVenue(value)}
allowClear
>
{venues.map(venue => (
<Option key={venue.id} value={venue.id}>{venue.name}</Option>
))}
</Select>
<Select
style={{ width: 200 }}
placeholder="选择活动类型"
onChange={(value) => setSelectedEventType(value)}
allowClear
>
{eventTypes.map(type => (
<Option key={type.id} value={type.id}>{type.name}</Option>
))}
</Select>
<Button
type="primary"
onClick={handlePredict}
loading={loading}
>
预测可用日期
</Button>
</div>
{availableDates.length > 0 && (
<div style={{ marginTop: '20px' }}>
<Table
columns={columns}
dataSource={availableDates}
pagination={false}
rowKey={(record, index) => index}
/>
</div>
)}
</Card>
{selectedVenue && selectedEventType && (
<Card title="可用日期日历视图">
<Calendar
dateCellRender={dateCellRender}
value={dateRange[0]}
onChange={(date) => setDateRange([date, date.add(1, 'year')])}
/>
</Card>
)}
</div>
);
};
export default ExhibitionScheduleQuery;
数据可视化与分析
使用ECharts进行趋势分析
// 趋势分析图表组件
import React from 'react';
import ReactECharts from 'echarts-for-react';
import moment from 'moment';
const TrendAnalysisChart = ({ historicalData, predictionData }) => {
const getOption = () => {
const dates = historicalData.map(d => d.date);
const actualValues = historicalData.map(d => d.actual);
const predictedValues = predictionData.map(d => d.date);
return {
title: {
text: '展览活动排期趋势分析',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['实际排期', '预测排期'],
top: 30
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
formatter: (value) => moment(value).format('MM-DD')
}
},
yAxis: {
type: 'value',
name: '活动数量'
},
series: [
{
name: '实际排期',
type: 'line',
data: actualValues,
smooth: true,
lineStyle: {
color: '#5470c6',
width: 2
},
itemStyle: {
color: '#5470c6'
}
},
{
name: '预测排期',
type: 'line',
data: predictedValues,
smooth: true,
lineStyle: {
color: '#91cc75',
width: 2,
type: 'dashed'
},
itemStyle: {
color: '#91cc75'
}
}
],
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
}
};
};
return <ReactECharts option={getOption()} style={{ height: '400px' }} />;
};
export default TrendAnalysisChart;
系统部署与运维
Docker部署配置
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_DB: exhibition_schedule
POSTGRES_USER: schedule_user
POSTGRES_PASSWORD: secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
app:
build: .
environment:
DATABASE_URL: postgresql://schedule_user:secure_password@db:5432/exhibition_schedule
REDIS_URL: redis://redis:6379
ports:
- "5000:5000"
depends_on:
- db
- redis
celery:
build: .
command: celery -A tasks worker --loglevel=info
environment:
DATABASE_URL: postgresql://schedule_user:secure_password@db:5432/exhibition_schedule
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
volumes:
postgres_data:
监控与日志
# 监控配置
import logging
from prometheus_client import Counter, Histogram, generate_latest
from flask import Response
# 定义监控指标
PREDICTION_REQUESTS = Counter('prediction_requests_total', 'Total prediction requests')
PREDICTION_DURATION = Histogram('prediction_duration_seconds', 'Prediction duration in seconds')
SCHEDULE_OPTIMIZATION_REQUESTS = Counter('schedule_optimization_requests_total', 'Total schedule optimization requests')
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('exhibition_schedule.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
@app.route('/metrics')
def metrics():
"""Prometheus指标接口"""
return Response(generate_latest(), mimetype='text/plain')
@app.route('/api/v1/predict/available-dates', methods=['POST'])
@PREDICTION_DURATION.time()
def predict_available_dates():
PREDICTION_REQUESTS.inc()
logger.info(f"Prediction request received: {request.get_json()}")
# ... 原有逻辑
总结
排期预测展览活动日期查询系统是一个复杂的智能调度系统,需要综合运用数据分析、机器学习、优化算法和Web开发技术。通过本文的详细介绍,您应该已经了解了:
- 系统架构设计:如何组织各个功能模块
- 数据库设计:核心数据表结构和关系
- 核心算法:日期预测和排期优化的实现
- API接口:RESTful接口设计和实现
- 前端界面:React组件和可视化展示
- 部署运维:Docker化部署和监控
实际部署时,需要根据具体业务需求调整算法参数、优化数据库索引、增加缓存策略,并持续监控系统性能。建议从小规模开始,逐步迭代优化,最终构建一个高效、可靠的展览活动排期预测查询系统。# 排期预测展览活动日期查询系统设计与实现指南
引言:理解排期预测与展览活动日期查询的重要性
在现代展览活动管理中,排期预测和日期查询是核心业务功能。展览活动通常涉及多个场地、不同类型的活动、复杂的资源分配以及多变的客户需求。一个高效的排期预测系统不仅能帮助活动组织者合理安排资源,还能为客户提供准确的日期查询服务,从而提升整体运营效率和客户满意度。
排期预测展览活动日期查询系统本质上是一个智能调度系统,它需要考虑多种约束条件,包括场地可用性、活动类型、季节性因素、历史数据趋势以及客户偏好等。通过科学的预测算法和高效的查询机制,系统能够为展览活动的规划和执行提供有力支持。
本文将详细介绍如何设计和实现一个完整的排期预测展览活动日期查询系统,包括系统架构设计、核心算法实现、数据库设计以及前端查询接口等关键内容。
系统架构设计
整体架构概述
一个完整的排期预测展览活动日期查询系统通常包含以下几个核心模块:
- 数据采集与存储模块:负责收集和存储展览活动相关的历史数据
- 预测分析模块:基于历史数据进行趋势分析和日期预测
- 排期优化模块:考虑多种约束条件进行最优排期安排
- 查询接口模块:提供用户友好的查询界面和API接口
- 可视化展示模块:以图表形式展示排期结果和预测趋势
技术栈选择
- 后端:Python(Flask/Django)或Node.js
- 数据库:PostgreSQL(支持地理空间数据)或MongoDB
- 预测算法:Prophet、ARIMA、LSTM神经网络
- 前端:React/Vue.js + ECharts/D3.js
- 缓存:Redis
- 消息队列:RabbitMQ/Kafka(用于异步处理预测任务)
数据库设计
核心数据表结构
展览活动排期系统需要设计合理的数据模型来存储活动信息、场地信息、历史排期记录等。
-- 场地信息表
CREATE TABLE venues (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
location VARCHAR(255),
capacity INT,
facilities TEXT[],
available_days INT[] -- 0-6表示周日到周六,1表示可用,0表示不可用
);
-- 活动类型表
CREATE TABLE event_types (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
typical_duration_days INT,
season_preference INT[] -- 偏好季节,如[3,4,5]表示春季
);
-- 展览活动表
CREATE TABLE exhibitions (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
event_type_id INT REFERENCES event_types(id),
venue_id INT REFERENCES venues(id),
start_date DATE,
end_date DATE,
status VARCHAR(50), -- 'planned', 'confirmed', 'completed', 'cancelled'
expected_attendees INT,
actual_attendees INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 历史排期记录表
CREATE TABLE schedule_history (
id SERIAL PRIMARY KEY,
exhibition_id INT REFERENCES exhibitions(id),
predicted_start_date DATE,
predicted_end_date DATE,
actual_start_date DATE,
actual_end_date DATE,
prediction_accuracy FLOAT,
model_version VARCHAR(50)
);
-- 客户查询记录表
CREATE TABLE customer_queries (
id SERIAL PRIMARY KEY,
customer_id INT,
preferred_venue_id INT REFERENCES venues(id),
event_type_id INT REFERENCES event_types(id),
preferred_date_range_start DATE,
preferred_date_range_end DATE,
query_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
result_exhibition_ids INT[]
);
数据关系说明
- venues表存储展览场地信息,包括位置、容量和每周可用日期
- event_types表定义不同类型的展览活动及其季节性偏好
- exhibitions表记录具体的展览活动安排,包括实际日期和状态
- schedule_history表保存预测与实际日期的对比数据,用于模型优化
- customer_queries表记录用户查询历史,用于分析用户偏好和优化推荐
核心算法实现
1. 基于历史数据的日期预测算法
展览活动日期预测需要考虑多种因素,包括季节性、场地约束、活动类型特征等。以下是使用Python和Prophet库实现的预测算法:
import pandas as pd
from prophet import Prophet
import numpy as np
from datetime import datetime, timedelta
from typing import List, Dict, Tuple
class ExhibitionDatePredictor:
def __init__(self):
self.models = {} # 按活动类型存储训练好的模型
self.historical_data = None
def load_historical_data(self, db_connection):
"""从数据库加载历史排期数据"""
query = """
SELECT
e.start_date as ds,
e.event_type_id,
et.name as event_type_name,
e.venue_id,
v.name as venue_name,
e.expected_attendees as y,
e.status
FROM exhibitions e
JOIN event_types et ON e.event_type_id = et.id
JOIN venues v ON e.venue_id = v.id
WHERE e.status = 'completed'
ORDER BY e.start_date
"""
self.historical_data = pd.read_sql(query, db_connection)
return self.historical_data
def train_models(self):
"""为每种活动类型训练预测模型"""
if self.historical_data is None:
raise ValueError("Historical data not loaded")
for event_type_id in self.historical_data['event_type_id'].unique():
event_data = self.historical_data[
self.historical_data['event_type_id'] == event_type_id
].copy()
# Prophet要求列名为ds和y
prophet_data = event_data[['start_date', 'expected_attendees']].rename(
columns={'start_date': 'ds', 'expected_attendees': 'y'}
)
# 初始化模型,添加季节性因素
model = Prophet(
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False,
seasonality_mode='multiplicative'
)
# 添加场地约束作为额外回归因子
# 这里简化处理,实际中可以添加更多特征
model.add_country_holidays(country_name='CN')
# 训练模型
model.fit(prophet_data)
self.models[event_type_id] = model
def predict_available_dates(self, event_type_id: int, venue_id: int,
start_date: datetime, end_date: datetime,
min_duration: int = 1) -> List[Dict]:
"""
预测指定活动类型在指定场地的可用日期范围
Args:
event_type_id: 活动类型ID
venue_id: 场地ID
start_date: 查询开始日期
end_date: 查询结束日期
min_duration: 最小持续天数
Returns:
可用日期范围列表
"""
if event_type_id not in self.models:
raise ValueError(f"No model trained for event_type_id {event_type_id}")
model = self.models[event_type_id]
# 生成未来日期的DataFrame
future_dates = model.make_future_dataframe(
periods=(end_date - start_date).days + 1,
freq='D'
)
# 预测每个日期的活动热度(attendees)
forecast = model.predict(future_dates)
# 获取场地可用性约束
venue_availability = self._get_venue_availability(venue_id)
# 筛选可用日期
available_slots = []
current_slot = None
for idx, row in forecast.iterrows():
date = row['ds']
# 检查日期是否在查询范围内
if date < start_date or date > end_date:
continue
# 检查场地是否可用(周几)
weekday = date.weekday() # 0=Monday, 6=Sunday
if not venue_availability[weekday]:
# 如果当前有正在构建的时段,需要结束它
if current_slot:
available_slots.append(current_slot)
current_slot = None
continue
# 检查活动热度是否过高(可选的业务规则)
# 这里假设热度超过某个阈值就不适合安排
if row['yhat'] > 50000: # 假设阈值
if current_slot:
available_slots.append(current_slot)
current_slot = None
continue
# 构建连续可用时段
if current_slot is None:
current_slot = {
'start_date': date,
'end_date': date,
'predicted_attendees': row['yhat'],
'confidence': 1 - row['yhat_lower'] / row['yhat_upper']
}
else:
# 检查是否连续
if date == current_slot['end_date'] + timedelta(days=1):
current_slot['end_date'] = date
current_slot['predicted_attendees'] = (
current_slot['predicted_attendees'] + row['yhat']
) / 2 # 平均热度
else:
# 不连续,结束当前时段
if (current_slot['end_date'] - current_slot['start_date']).days + 1 >= min_duration:
available_slots.append(current_slot)
current_slot = {
'start_date': date,
'end_date': date,
'predicted_attendees': row['yhat'],
'confidence': 1 - row['yhat_lower'] / row['yhat_upper']
}
# 添加最后一个时段
if current_slot and (current_slot['end_date'] - current_slot['start_date']).days + 1 >= min_duration:
available_slots.append(current_slot)
return available_slots
def _get_venue_availability(self, venue_id: int) -> List[bool]:
"""获取场地的周几可用性(0=周一到6=周日)"""
# 这里应该从数据库查询,简化为示例
# 假设大多数场地周一到周日都可用,但有些场地可能周末不可用
if venue_id % 2 == 0:
# 场地A:周一到周五可用
return [True, True, True, True, True, False, False]
else:
# 场地B:每天都可用
return [True, True, True, True, True, True, True]
def evaluate_prediction_accuracy(self, event_type_id: int) -> float:
"""评估模型预测准确率"""
if event_type_id not in self.models:
return 0.0
# 获取该活动类型的历史数据
historical = self.historical_data[
self.historical_data['event_type_id'] == event_type_id
]
if len(historical) < 5:
return 0.0
# 使用交叉验证评估
# 这里简化处理,实际应该使用更严格的验证方法
from sklearn.metrics import mean_absolute_error
predictions = []
actuals = []
for idx, row in historical.iterrows():
# 预测该日期的活动热度
ds = pd.DataFrame({'ds': [row['start_date']]})
forecast = self.models[event_type_id].predict(ds)
predictions.append(forecast['yhat'].values[0])
actuals.append(row['expected_attendees'])
mae = mean_absolute_error(actuals, predictions)
accuracy = 1 - (mae / np.mean(actuals))
return max(0, accuracy) # 确保不为负数
# 使用示例
if __name__ == "__main__":
# 初始化预测器
predictor = ExhibitionDatePredictor()
# 模拟数据加载(实际应从数据库)
# predictor.load_historical_data(db_connection)
# 训练模型
# predictor.train_models()
# 预测可用日期
# available_dates = predictor.predict_available_dates(
# event_type_id=1,
# venue_id=1,
# start_date=datetime(2024, 1, 1),
# end_date=datetime(2024, 12, 31),
# min_duration=3
# )
# print(available_dates)
2. 排期优化算法
在预测的基础上,还需要考虑多约束条件的排期优化:
from ortools.sat.python import cp_model
from typing import List, Dict
import datetime
class ScheduleOptimizer:
def __init__(self):
self.model = cp_model.CpModel()
self.solver = cp_model.CpSolver()
def optimize_schedule(self, requests: List[Dict], venues: List[Dict]) -> Dict:
"""
优化排期,最大化场地利用率和客户满意度
Args:
requests: 客户请求列表,包含活动类型、期望日期、持续天数等
venues: 场地列表,包含容量、可用日期等
Returns:
优化后的排期结果
"""
# 创建变量
schedule_vars = {}
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 为每个请求-场地组合创建布尔变量
var_name = f"schedule_{i}_{j}"
schedule_vars[(i, j)] = self.model.NewBoolVar(var_name)
# 约束1:每个请求只能分配一个场地
for i, req in enumerate(requests):
self.model.Add(sum(schedule_vars[(i, j)] for j in range(len(venues))) == 1)
# 约束2:场地在时间上不能重叠(简化处理,实际需要更复杂的时间建模)
# 这里假设我们已经将时间离散化为时间段
for j, venue in enumerate(venues):
for time_slot in range(365): # 一年中的每一天
# 计算该时间段内有多少活动被安排
overlapping = sum(
schedule_vars[(i, j)]
for i, req in enumerate(requests)
if self._is_activity_in_slot(req, time_slot)
)
# 假设场地每天只能容纳一个活动
self.model.Add(overlapping <= 1)
# 约束3:场地容量约束
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
if req['expected_attendees'] > venue['capacity']:
# 如果活动预期人数超过场地容量,则不能分配
self.model.Add(schedule_vars[(i, j)] == 0)
# 约束4:场地可用日期约束
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 检查请求的日期是否在场地可用日期内
if not self._check_venue_availability(req, venue):
self.model.Add(schedule_vars[(i, j)] == 0)
# 目标函数:最大化总满意度(基于预测准确率和容量匹配度)
objective_terms = []
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
# 满意度评分:容量匹配度 + 预测准确率
capacity_score = 1 - abs(req['expected_attendees'] - venue['capacity']) / venue['capacity']
# 假设我们有预测准确率
prediction_score = req.get('prediction_accuracy', 0.8)
score = capacity_score * prediction_score
objective_terms.append(schedule_vars[(i, j)] * score * 100)
self.model.Maximize(sum(objective_terms))
# 求解
status = self.solver.Solve(self.model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
result = []
for i, req in enumerate(requests):
for j, venue in enumerate(venues):
if self.solver.Value(schedule_vars[(i, j)]) == 1:
result.append({
'request_id': req['id'],
'venue_id': venue['id'],
'venue_name': venue['name'],
'scheduled_date': req['preferred_start_date'],
'satisfaction_score': self.solver.ObjectiveValue() / len(requests)
})
return {'status': 'success', 'schedule': result}
else:
return {'status': 'failed', 'message': 'No feasible schedule found'}
def _is_activity_in_slot(self, req: Dict, time_slot: int) -> bool:
"""检查活动是否在指定时间段内"""
start = req['preferred_start_date'].timetuple().tm_yday
duration = req.get('duration_days', 1)
return start <= time_slot < start + duration
def _check_venue_availability(self, req: Dict, venue: Dict) -> bool:
"""检查场地在请求日期是否可用"""
# 检查周几可用性
start_date = req['preferred_start_date']
weekday = start_date.weekday()
# venue['available_days'] 是一个长度为7的列表,表示周一到周日
if not venue['available_days'][weekday]:
return False
# 可以添加更多检查,如节假日、已有排期等
return True
# 使用示例
if __name__ == "__main__":
optimizer = ScheduleOptimizer()
# 示例请求
requests = [
{
'id': 1,
'event_type_id': 1,
'expected_attendees': 2000,
'preferred_start_date': datetime.date(2024, 3, 15),
'duration_days': 3,
'prediction_accuracy': 0.85
},
{
'id': 2,
'event_type_id': 2,
'expected_attendees': 5000,
'preferred_start_date': datetime.date(2024, 3, 20),
'duration_days': 5,
'prediction_accuracy': 0.92
}
]
venues = [
{
'id': 1,
'name': '主展厅',
'capacity': 3000,
'available_days': [True, True, True, True, True, True, True] # 周一到周日
},
{
'id': 2,
'name': '多功能厅',
'capacity': 6000,
'available_days': [True, True, True, True, True, False, False] # 周一到周五
}
]
# 优化排期
result = optimizer.optimize_schedule(requests, venues)
print(result)
查询接口设计
RESTful API设计
from flask import Flask, request, jsonify
from datetime import datetime, timedelta
import json
app = Flask(__name__)
# 初始化预测器和优化器
predictor = ExhibitionDatePredictor()
optimizer = ScheduleOptimizer()
@app.route('/api/v1/predict/available-dates', methods=['POST'])
def predict_available_dates():
"""
预测指定条件下的可用日期范围
请求体示例:
{
"event_type_id": 1,
"venue_id": 1,
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"min_duration": 3
}
"""
try:
data = request.get_json()
# 参数验证
required_fields = ['event_type_id', 'venue_id', 'start_date', 'end_date']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Missing required field: {field}'}), 400
# 转换日期
start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date()
end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date()
# 调用预测器
available_dates = predictor.predict_available_dates(
event_type_id=data['event_type_id'],
venue_id=data['venue_id'],
start_date=start_date,
end_date=end_date,
min_duration=data.get('min_duration', 1)
)
return jsonify({
'status': 'success',
'data': available_dates,
'count': len(available_dates)
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/schedule/optimize', methods=['POST'])
def optimize_schedule():
"""
优化排期
请求体示例:
{
"requests": [
{
"id": 1,
"event_type_id": 1,
"expected_attendees": 2000,
"preferred_start_date": "2024-03-15",
"duration_days": 3
}
],
"venues": [
{
"id": 1,
"name": "主展厅",
"capacity": 3000,
"available_days": [1,1,1,1,1,1,1]
}
]
}
"""
try:
data = request.get_json()
# 转换日期格式
for req in data['requests']:
req['preferred_start_date'] = datetime.strptime(
req['preferred_start_date'], '%Y-%m-%d'
).date()
# 调用优化器
result = optimizer.optimize_schedule(
data['requests'],
data['venues']
)
return jsonify(result)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/query/history', methods=['GET'])
def get_query_history():
"""
获取查询历史记录
支持分页和过滤
"""
try:
page = int(request.args.get('page', 1))
per_page = int(request.args.get('per_page', 20))
customer_id = request.args.get('customer_id')
# 从数据库查询(示例代码)
# 实际应该查询customer_queries表
history = [
{
'id': 1,
'customer_id': customer_id,
'query_timestamp': '2024-01-15T10:30:00',
'preferred_venue_id': 1,
'event_type_id': 1,
'preferred_date_range': '2024-03-01 to 2024-03-31',
'result_count': 5
}
]
return jsonify({
'status': 'success',
'data': history,
'pagination': {
'page': page,
'per_page': per_page,
'total': len(history)
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/v1/venue/availability', methods=['GET'])
def get_venue_availability():
"""
查询场地在指定日期范围内的可用性
"""
try:
venue_id = request.args.get('venue_id')
start_date = datetime.strptime(request.args.get('start_date'), '%Y-%m-%d').date()
end_date = datetime.strptime(request.args.get('end_date'), '%Y-%m-%d').date()
# 这里应该查询数据库中的排期记录
# 示例:检查是否有已排期的活动
booked_dates = [] # 从数据库查询已排期日期
# 生成可用性日历
availability = []
current_date = start_date
while current_date <= end_date:
is_available = current_date not in booked_dates
availability.append({
'date': current_date.isoformat(),
'available': is_available,
'weekday': current_date.strftime('%A')
})
current_date += timedelta(days=1)
return jsonify({
'status': 'success',
'venue_id': venue_id,
'availability': availability
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
API使用示例
使用curl测试API:
# 预测可用日期
curl -X POST http://localhost:5000/api/v1/predict/available-dates \
-H "Content-Type: application/json" \
-d '{
"event_type_id": 1,
"venue_id": 1,
"start_date": "2024-01-01",
"end_date": "2024-12-31",
"min_duration": 3
}'
# 优化排期
curl -X POST http://localhost:5000/api/v1/schedule/optimize \
-H "Content-Type: application/json" \
-d '{
"requests": [
{
"id": 1,
"event_type_id": 1,
"expected_attendees": 2000,
"preferred_start_date": "2024-03-15",
"duration_days": 3
}
],
"venues": [
{
"id": 1,
"name": "主展厅",
"capacity": 3000,
"available_days": [1,1,1,1,1,1,1]
}
]
}'
# 查询场地可用性
curl -X GET "http://localhost:5000/api/v1/venue/availability?venue_id=1&start_date=2024-03-01&end_date=2024-03-31"
前端查询界面设计
React组件示例
import React, { useState, useEffect } from 'react';
import { Calendar, Select, Button, Card, Table, message } from 'antd';
import axios from 'axios';
import moment from 'moment';
const { Option } = Select;
const ExhibitionScheduleQuery = () => {
const [venues, setVenues] = useState([]);
const [eventTypes, setEventTypes] = useState([]);
const [selectedVenue, setSelectedVenue] = useState(null);
const [selectedEventType, setSelectedEventType] = useState(null);
const [dateRange, setDateRange] = useState([moment(), moment().add(1, 'year')]);
const [availableDates, setAvailableDates] = useState([]);
const [loading, setLoading] = useState(false);
// 加载基础数据
useEffect(() => {
fetchVenues();
fetchEventTypes();
}, []);
const fetchVenues = async () => {
try {
const response = await axios.get('/api/v1/venues');
setVenues(response.data.data);
} catch (error) {
message.error('加载场地数据失败');
}
};
const fetchEventTypes = async () => {
try {
const response = await axios.get('/api/v1/event-types');
setEventTypes(response.data.data);
} catch (error) {
message.error('加载活动类型数据失败');
}
};
const handlePredict = async () => {
if (!selectedVenue || !selectedEventType) {
message.warning('请选择场地和活动类型');
return;
}
setLoading(true);
try {
const response = await axios.post('/api/v1/predict/available-dates', {
event_type_id: selectedEventType,
venue_id: selectedVenue,
start_date: dateRange[0].format('YYYY-MM-DD'),
end_date: dateRange[1].format('YYYY-MM-DD'),
min_duration: 3
});
setAvailableDates(response.data.data);
message.success(`找到 ${response.data.count} 个可用时段`);
} catch (error) {
message.error('预测失败:' + error.message);
} finally {
setLoading(false);
}
};
const columns = [
{
title: '开始日期',
dataIndex: 'start_date',
key: 'start_date',
render: (text) => moment(text).format('YYYY-MM-DD')
},
{
title: '结束日期',
dataIndex: 'end_date',
key: 'end_date',
render: (text) => moment(text).format('YYYY-MM-DD')
},
{
title: '持续天数',
key: 'duration',
render: (text, record) => {
const start = moment(record.start_date);
const end = moment(record.end_date);
return end.diff(start, 'days') + 1;
}
},
{
title: '预测人数',
dataIndex: 'predicted_attendees',
key: 'predicted_attendees'
},
{
title: '置信度',
dataIndex: 'confidence',
key: 'confidence',
render: (text) => `${(text * 100).toFixed(1)}%`
}
];
// 自定义日历单元格渲染
const dateCellRender = (value) => {
const dateStr = value.format('YYYY-MM-DD');
const slot = availableDates.find(slot => {
const start = moment(slot.start_date);
const end = moment(slot.end_date);
return value.isBetween(start, end, 'day', '[]');
});
if (slot) {
return (
<div style={{
background: '#52c41a',
color: 'white',
padding: '2px',
borderRadius: '4px',
fontSize: '10px'
}}>
可用
</div>
);
}
return null;
};
return (
<div style={{ padding: '20px' }}>
<Card title="展览活动排期预测查询" style={{ marginBottom: '20px' }}>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', marginBottom: '10px' }}>
<Select
style={{ width: 200 }}
placeholder="选择场地"
onChange={(value) => setSelectedVenue(value)}
allowClear
>
{venues.map(venue => (
<Option key={venue.id} value={venue.id}>{venue.name}</Option>
))}
</Select>
<Select
style={{ width: 200 }}
placeholder="选择活动类型"
onChange={(value) => setSelectedEventType(value)}
allowClear
>
{eventTypes.map(type => (
<Option key={type.id} value={type.id}>{type.name}</Option>
))}
</Select>
<Button
type="primary"
onClick={handlePredict}
loading={loading}
>
预测可用日期
</Button>
</div>
{availableDates.length > 0 && (
<div style={{ marginTop: '20px' }}>
<Table
columns={columns}
dataSource={availableDates}
pagination={false}
rowKey={(record, index) => index}
/>
</div>
)}
</Card>
{selectedVenue && selectedEventType && (
<Card title="可用日期日历视图">
<Calendar
dateCellRender={dateCellRender}
value={dateRange[0]}
onChange={(date) => setDateRange([date, date.add(1, 'year')])}
/>
</Card>
)}
</div>
);
};
export default ExhibitionScheduleQuery;
数据可视化与分析
使用ECharts进行趋势分析
// 趋势分析图表组件
import React from 'react';
import ReactECharts from 'echarts-for-react';
import moment from 'moment';
const TrendAnalysisChart = ({ historicalData, predictionData }) => {
const getOption = () => {
const dates = historicalData.map(d => d.date);
const actualValues = historicalData.map(d => d.actual);
const predictedValues = predictionData.map(d => d.date);
return {
title: {
text: '展览活动排期趋势分析',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['实际排期', '预测排期'],
top: 30
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
formatter: (value) => moment(value).format('MM-DD')
}
},
yAxis: {
type: 'value',
name: '活动数量'
},
series: [
{
name: '实际排期',
type: 'line',
data: actualValues,
smooth: true,
lineStyle: {
color: '#5470c6',
width: 2
},
itemStyle: {
color: '#5470c6'
}
},
{
name: '预测排期',
type: 'line',
data: predictedValues,
smooth: true,
lineStyle: {
color: '#91cc75',
width: 2,
type: 'dashed'
},
itemStyle: {
color: '#91cc75'
}
}
],
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
}
};
};
return <ReactECharts option={getOption()} style={{ height: '400px' }} />;
};
export default TrendAnalysisChart;
系统部署与运维
Docker部署配置
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres:13
environment:
POSTGRES_DB: exhibition_schedule
POSTGRES_USER: schedule_user
POSTGRES_PASSWORD: secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
app:
build: .
environment:
DATABASE_URL: postgresql://schedule_user:secure_password@db:5432/exhibition_schedule
REDIS_URL: redis://redis:6379
ports:
- "5000:5000"
depends_on:
- db
- redis
celery:
build: .
command: celery -A tasks worker --loglevel=info
environment:
DATABASE_URL: postgresql://schedule_user:secure_password@db:5432/exhibition_schedule
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
volumes:
postgres_data:
监控与日志
# 监控配置
import logging
from prometheus_client import Counter, Histogram, generate_latest
from flask import Response
# 定义监控指标
PREDICTION_REQUESTS = Counter('prediction_requests_total', 'Total prediction requests')
PREDICTION_DURATION = Histogram('prediction_duration_seconds', 'Prediction duration in seconds')
SCHEDULE_OPTIMIZATION_REQUESTS = Counter('schedule_optimization_requests_total', 'Total schedule optimization requests')
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('exhibition_schedule.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
@app.route('/metrics')
def metrics():
"""Prometheus指标接口"""
return Response(generate_latest(), mimetype='text/plain')
@app.route('/api/v1/predict/available-dates', methods=['POST'])
@PREDICTION_DURATION.time()
def predict_available_dates():
PREDICTION_REQUESTS.inc()
logger.info(f"Prediction request received: {request.get_json()}")
# ... 原有逻辑
总结
排期预测展览活动日期查询系统是一个复杂的智能调度系统,需要综合运用数据分析、机器学习、优化算法和Web开发技术。通过本文的详细介绍,您应该已经了解了:
- 系统架构设计:如何组织各个功能模块
- 数据库设计:核心数据表结构和关系
- 核心算法:日期预测和排期优化的实现
- API接口:RESTful接口设计和实现
- 前端界面:React组件和可视化展示
- 部署运维:Docker化部署和监控
实际部署时,需要根据具体业务需求调整算法参数、优化数据库索引、增加缓存策略,并持续监控系统性能。建议从小规模开始,逐步迭代优化,最终构建一个高效、可靠的展览活动排期预测查询系统。
