Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,90 +1,645 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
-
import
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
-
|
|
|
6 |
|
7 |
-
st.title('影片放映时间表统计')
|
8 |
|
9 |
-
#
|
10 |
-
uploaded_file = st.file_uploader("上传“影片放映时间表.xlsx”文件", type=['xlsx'])
|
11 |
-
ad_duration = st.number_input('输入每个广告的时长(分钟)', min_value=0, value=5)
|
12 |
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
try:
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
else:
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
except Exception as e:
|
90 |
-
st.error(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import requests
|
5 |
+
import time
|
6 |
+
from collections import defaultdict
|
7 |
+
import datetime
|
8 |
+
import altair as alt
|
9 |
|
10 |
+
# Set page layout to wide mode and set page title
|
11 |
+
st.set_page_config(layout="wide", page_title="影城效率与内容分析工具")
|
12 |
|
|
|
13 |
|
14 |
+
# --- Helper Functions ---
|
|
|
|
|
15 |
|
16 |
+
def clean_movie_title(title):
|
17 |
+
if not isinstance(title, str):
|
18 |
+
return title
|
19 |
+
return title.split(' ', 1)[0]
|
20 |
+
|
21 |
+
|
22 |
+
def style_efficiency(row):
|
23 |
+
green = 'background-color: #E6F5E6;' # Light Green
|
24 |
+
red = 'background-color: #FFE5E5;' # Light Red
|
25 |
+
default = ''
|
26 |
+
styles = [default] * len(row)
|
27 |
+
seat_efficiency = row.get('座次效率', 0)
|
28 |
+
session_efficiency = row.get('场次效率', 0)
|
29 |
+
if seat_efficiency > 1.5 or session_efficiency > 1.5:
|
30 |
+
styles = [green] * len(row)
|
31 |
+
elif seat_efficiency < 0.5 or session_efficiency < 0.5:
|
32 |
+
styles = [red] * len(row)
|
33 |
+
return styles
|
34 |
+
|
35 |
+
|
36 |
+
def process_and_analyze_data(df):
|
37 |
+
if df.empty:
|
38 |
+
return pd.DataFrame()
|
39 |
+
analysis_df = df.groupby('影片名称_清理后').agg(
|
40 |
+
座位数=('座位数', 'sum'),
|
41 |
+
场次=('影片名称_清理后', 'size'),
|
42 |
+
票房=('总收入', 'sum'),
|
43 |
+
人次=('总人次', 'sum')
|
44 |
+
).reset_index()
|
45 |
+
analysis_df.rename(columns={'影片名称_清理后': '影片'}, inplace=True)
|
46 |
+
analysis_df = analysis_df.sort_values(by='票房', ascending=False).reset_index(drop=True)
|
47 |
+
total_seats = analysis_df['座位数'].sum()
|
48 |
+
total_sessions = analysis_df['场次'].sum()
|
49 |
+
total_revenue = analysis_df['票房'].sum()
|
50 |
+
analysis_df['均价'] = np.divide(analysis_df['票房'], analysis_df['人次']).fillna(0)
|
51 |
+
analysis_df['座次比'] = np.divide(analysis_df['座位数'], total_seats).fillna(0)
|
52 |
+
analysis_df['场次比'] = np.divide(analysis_df['场次'], total_sessions).fillna(0)
|
53 |
+
analysis_df['票房比'] = np.divide(analysis_df['票房'], total_revenue).fillna(0)
|
54 |
+
analysis_df['座次效率'] = np.divide(analysis_df['票房比'], analysis_df['座次比']).fillna(0)
|
55 |
+
analysis_df['场次效率'] = np.divide(analysis_df['票房比'], analysis_df['场次比']).fillna(0)
|
56 |
+
final_columns = ['影片', '座位数', '场次', '票房', '人次', '均价', '座次比', '场次比', '票房比', '座次效率',
|
57 |
+
'场次效率']
|
58 |
+
analysis_df = analysis_df[final_columns]
|
59 |
+
return analysis_df
|
60 |
+
|
61 |
+
|
62 |
+
def get_circled_number(hall_name):
|
63 |
+
mapping = {'1': '①', '2': '②', '3': '③', '4': '④', '5': '⑤', '6': '⑥', '7': '⑦', '8': '⑧', '9': '⑨'}
|
64 |
+
num_str = ''.join(filter(str.isdigit, hall_name))
|
65 |
+
return mapping.get(num_str, '')
|
66 |
+
|
67 |
+
|
68 |
+
def format_play_time(time_str):
|
69 |
+
if not time_str or not isinstance(time_str, str): return None
|
70 |
try:
|
71 |
+
parts = time_str.split(':');
|
72 |
+
hours = int(parts[0]);
|
73 |
+
minutes = int(parts[1])
|
74 |
+
return hours * 60 + minutes
|
75 |
+
except (ValueError, IndexError):
|
76 |
+
return None
|
77 |
+
|
78 |
+
|
79 |
+
def add_tms_locations_to_analysis(analysis_df, tms_movie_list):
|
80 |
+
locations = []
|
81 |
+
for index, row in analysis_df.iterrows():
|
82 |
+
movie_title = row['影片']
|
83 |
+
found_versions = []
|
84 |
+
for tms_movie in tms_movie_list:
|
85 |
+
if tms_movie['assert_name'].startswith(movie_title):
|
86 |
+
version_name = tms_movie['assert_name'].replace(movie_title, '').strip()
|
87 |
+
circled_halls = " ".join(sorted([get_circled_number(h) for h in tms_movie['halls']]))
|
88 |
+
if version_name:
|
89 |
+
found_versions.append(f"{version_name}:{circled_halls}")
|
90 |
+
else:
|
91 |
+
found_versions.append(circled_halls)
|
92 |
+
locations.append('|'.join(found_versions))
|
93 |
+
analysis_df['影片所在影厅位置'] = locations
|
94 |
+
return analysis_df
|
95 |
+
|
96 |
+
|
97 |
+
def get_chinese_holidays_2025():
|
98 |
+
holidays = set()
|
99 |
+
holidays.add(datetime.date(2025, 1, 1))
|
100 |
+
holidays.update([datetime.date(2025, 1, 28), datetime.date(2025, 1, 29), datetime.date(2025, 1, 30),
|
101 |
+
datetime.date(2025, 1, 31), datetime.date(2025, 2, 1), datetime.date(2025, 2, 2),
|
102 |
+
datetime.date(2025, 2, 3)])
|
103 |
+
holidays.update([datetime.date(2025, 4, 4), datetime.date(2025, 4, 5), datetime.date(2025, 4, 6)])
|
104 |
+
holidays.update([datetime.date(2025, 5, 1), datetime.date(2025, 5, 2), datetime.date(2025, 5, 3),
|
105 |
+
datetime.date(2025, 5, 4), datetime.date(2025, 5, 5)])
|
106 |
+
holidays.update([datetime.date(2025, 5, 30), datetime.date(2025, 5, 31), datetime.date(2025, 6, 1)])
|
107 |
+
holidays.add(datetime.date(2025, 10, 6))
|
108 |
+
holidays.update([datetime.date(2025, 10, 1), datetime.date(2025, 10, 2), datetime.date(2025, 10, 3),
|
109 |
+
datetime.date(2025, 10, 4), datetime.date(2025, 10, 5), datetime.date(2025, 10, 6),
|
110 |
+
datetime.date(2025, 10, 7)])
|
111 |
+
return holidays
|
112 |
+
|
113 |
+
|
114 |
+
def plot_daily_box_office(df, selected_movie='全部影片'):
|
115 |
+
if selected_movie != '全部影片':
|
116 |
+
plot_df = df[df['影片名称_清理后'] == selected_movie].copy()
|
117 |
+
else:
|
118 |
+
plot_df = df.copy()
|
119 |
+
|
120 |
+
if plot_df.empty:
|
121 |
+
st.warning(f"影片《{selected_movie}》在所分析的文件中没有找到数据。")
|
122 |
+
return None
|
123 |
+
|
124 |
+
daily_revenue = plot_df.groupby('放映日期')['总收入'].sum().reset_index()
|
125 |
+
daily_revenue.rename(columns={'放映日期': '日期', '总收入': '票房'}, inplace=True)
|
126 |
+
|
127 |
+
total_box_office = daily_revenue['票房'].sum()
|
128 |
+
chart_title = f'每日票房表现 - {selected_movie} | 总票房: {total_box_office:,.0f} 元'
|
129 |
+
|
130 |
+
start_date = pd.to_datetime(df['放映日期'].min())
|
131 |
+
end_date = pd.to_datetime(df['放映日期'].max())
|
132 |
+
full_date_range = pd.to_datetime(pd.date_range(start=start_date, end=end_date, freq='D'))
|
133 |
+
daily_revenue['日期'] = pd.to_datetime(daily_revenue['日期'])
|
134 |
+
daily_revenue = pd.merge(pd.DataFrame({'日期': full_date_range}), daily_revenue, on='日期', how='left').fillna(0)
|
135 |
+
|
136 |
+
holidays = get_chinese_holidays_2025()
|
137 |
+
daily_revenue['day_of_week'] = daily_revenue['日期'].dt.dayofweek
|
138 |
+
daily_revenue['类型'] = daily_revenue.apply(
|
139 |
+
lambda row: '节假日' if row['日期'].date() in holidays else (
|
140 |
+
'周末' if row['day_of_week'] in [4, 5, 6] else '工作日'),
|
141 |
+
axis=1
|
142 |
+
)
|
143 |
+
|
144 |
+
chart = alt.Chart(daily_revenue).mark_bar().encode(
|
145 |
+
x=alt.X('日期:T', title='日期', axis=alt.Axis(labelAngle=-45, format='%m-%d')),
|
146 |
+
y=alt.Y('票房:Q', title='票房 (元)', scale=alt.Scale(domainMin=0)),
|
147 |
+
color=alt.Color('类型:N',
|
148 |
+
scale=alt.Scale(domain=['工作日', '周末', '节假日'], range=['#87CEEB', '#FFA500', '#FF4500']),
|
149 |
+
legend=alt.Legend(title="日期类型")),
|
150 |
+
tooltip=[alt.Tooltip('日期:T', format='%Y-%m-%d', title='日期'),
|
151 |
+
alt.Tooltip('票房:Q', format=',.2f', title='票房'),
|
152 |
+
alt.Tooltip('类型:N', title='类型')]
|
153 |
+
).properties(
|
154 |
+
title=chart_title
|
155 |
+
).interactive()
|
156 |
+
|
157 |
+
return chart
|
158 |
+
|
159 |
+
|
160 |
+
def round_time_to_5min(t_datetime):
|
161 |
+
if not isinstance(t_datetime, datetime.datetime):
|
162 |
+
if isinstance(t_datetime, datetime.time):
|
163 |
+
t_datetime = datetime.datetime.combine(datetime.date.today(), t_datetime)
|
164 |
else:
|
165 |
+
return None
|
166 |
+
|
167 |
+
minute = (t_datetime.minute // 5) * 5
|
168 |
+
rounded_datetime = t_datetime.replace(minute=minute, second=0, microsecond=0)
|
169 |
+
return rounded_datetime.time()
|
170 |
+
|
171 |
+
|
172 |
+
# --- REQUIREMENT 1: New function to plot daily box office by time period ---
|
173 |
+
def plot_daily_box_office_by_time(df, selected_movie='全部影片'):
|
174 |
+
if selected_movie != '全部影片':
|
175 |
+
plot_df = df[df['影片名称_清理后'] == selected_movie].copy()
|
176 |
+
else:
|
177 |
+
plot_df = df.copy()
|
178 |
+
|
179 |
+
if plot_df.empty:
|
180 |
+
return
|
181 |
+
|
182 |
+
plot_df['时间点'] = plot_df['放映时间'].apply(round_time_to_5min)
|
183 |
+
|
184 |
+
time_revenue = plot_df.groupby('时间点')['总收入'].sum().reset_index()
|
185 |
+
time_revenue.rename(columns={'总收入': '票房'}, inplace=True)
|
186 |
+
time_revenue['时间点'] = time_revenue['时间点'].apply(lambda t: t.strftime('%H:%M'))
|
187 |
+
|
188 |
+
chart_title = f'影城每日时间段票房表现 - {selected_movie}'
|
189 |
+
chart = alt.Chart(time_revenue).mark_bar().encode(
|
190 |
+
x=alt.X('时间点:N', title='时间点', sort=None, axis=alt.Axis(labelAngle=-45)),
|
191 |
+
y=alt.Y('票房:Q', title='票房 (元)'),
|
192 |
+
tooltip=[
|
193 |
+
alt.Tooltip('时间点:N', title='时间点'),
|
194 |
+
alt.Tooltip('票房:Q', format=',.2f', title='票房')
|
195 |
+
]
|
196 |
+
).properties(
|
197 |
+
title=chart_title
|
198 |
+
).interactive()
|
199 |
+
|
200 |
+
st.altair_chart(chart, use_container_width=True)
|
201 |
+
|
202 |
+
|
203 |
+
# --- Original time efficiency function (for the first tab) ---
|
204 |
+
def plot_time_efficiency_analysis(df):
|
205 |
+
df_filtered = df[(df['放映时间'] >= datetime.time(9, 30)) & (df['放映时间'] <= datetime.time(23, 59))].copy()
|
206 |
+
if df_filtered.empty:
|
207 |
+
st.warning("在 9:30 - 23:59 时间段内没有找到场次数据。")
|
208 |
+
return
|
209 |
+
|
210 |
+
df_filtered['时间点'] = df_filtered['放映时间'].apply(round_time_to_5min)
|
211 |
+
|
212 |
+
total_revenue_full_day = df['总收入'].sum()
|
213 |
+
total_seats_full_day = df['座位数'].sum()
|
214 |
+
total_sessions_full_day = len(df)
|
215 |
+
|
216 |
+
if total_revenue_full_day == 0 or total_seats_full_day == 0 or total_sessions_full_day == 0:
|
217 |
+
st.warning("总收入、总座位数或总场次数为零,无法计算效率。")
|
218 |
+
return
|
219 |
+
|
220 |
+
time_analysis = df_filtered.groupby(['放映日期', '时间点']).agg(
|
221 |
+
票房=('总收入', 'sum'),
|
222 |
+
座位数=('座位数', 'sum'),
|
223 |
+
场次=('场次', 'size'),
|
224 |
+
).reset_index()
|
225 |
+
|
226 |
+
time_analysis['票房比'] = time_analysis['票房'] / total_revenue_full_day
|
227 |
+
time_analysis['座次比'] = time_analysis['座位数'] / total_seats_full_day
|
228 |
+
time_analysis['场次比'] = time_analysis['场次'] / total_sessions_full_day
|
229 |
+
time_analysis['座次效率'] = (time_analysis['票房比'] / time_analysis['座次比']).fillna(0)
|
230 |
+
time_analysis['场次效率'] = (time_analysis['票房比'] / time_analysis['场次比']).fillna(0)
|
231 |
+
|
232 |
+
avg_time_efficiency = time_analysis.groupby('时间点')[['座次效率', '场次效率']].mean().reset_index()
|
233 |
+
avg_time_efficiency['时间点'] = avg_time_efficiency['时间点'].apply(lambda t: t.strftime('%H:%M'))
|
234 |
+
|
235 |
+
source = avg_time_efficiency.melt(id_vars=['时间点'], value_vars=['座次效率', '场次效率'], var_name='效率类型',
|
236 |
+
value_name='效率值')
|
237 |
+
chart = alt.Chart(source).mark_bar().encode(
|
238 |
+
x=alt.X('时间点:N', title='时间点', sort=None, axis=alt.Axis(labelAngle=-45)),
|
239 |
+
y=alt.Y('效率值:Q', title='平均效率'),
|
240 |
+
color=alt.Color('效率类型:N', title='效率类型'),
|
241 |
+
xOffset='效率类型:N',
|
242 |
+
tooltip=[alt.Tooltip('时间点:N'), alt.Tooltip('效率类型:N'), alt.Tooltip('效率值:Q', format='.2f')]
|
243 |
+
).properties(title='每日时间点平均效率分析 (对比全天)').interactive()
|
244 |
+
st.altair_chart(chart, use_container_width=True)
|
245 |
+
|
246 |
+
|
247 |
+
# --- Original movie time efficiency function (for the second tab) ---
|
248 |
+
def plot_movie_time_efficiency_analysis(df, selected_movie):
|
249 |
+
if selected_movie == '全部影片':
|
250 |
+
st.info("请选择一部具体的影片进行分析。")
|
251 |
+
return
|
252 |
+
|
253 |
+
df_movie = df[df['影片名称_清理后'] == selected_movie].copy()
|
254 |
+
df_movie = df_movie[
|
255 |
+
(df_movie['放映时间'] >= datetime.time(9, 30)) & (df_movie['放映时间'] <= datetime.time(23, 59))]
|
256 |
+
if df_movie.empty:
|
257 |
+
st.warning(f"在 9:30 - 23:59 时间段内没有找到影片《{selected_movie}》的场次数据。")
|
258 |
+
return
|
259 |
+
|
260 |
+
df_movie['时间点'] = df_movie['放映时间'].apply(round_time_to_5min)
|
261 |
+
daily_totals = df.groupby('放映日期').agg(总票房=('总收入', 'sum'), 总座位数=('座位数', 'sum'),
|
262 |
+
总场次数=('场次', 'sum')).reset_index()
|
263 |
+
if daily_totals.empty:
|
264 |
+
st.warning("无法计算每日总计数据,分析中止。")
|
265 |
+
return
|
266 |
+
|
267 |
+
df_movie = pd.merge(df_movie, daily_totals, on='放映日期')
|
268 |
+
df_movie = df_movie[(df_movie['总票房'] > 0) & (df_movie['总座位数'] > 0) & (df_movie['总场次数'] > 0)]
|
269 |
+
|
270 |
+
df_movie['票房比'] = df_movie['总收入'] / df_movie['总票房']
|
271 |
+
df_movie['座次比'] = df_movie['座位数'] / df_movie['总座位数']
|
272 |
+
df_movie['场次比'] = 1 / df_movie['总场次数']
|
273 |
+
df_movie['座次效率'] = (df_movie['票房比'] / df_movie['座次比']).fillna(0)
|
274 |
+
df_movie['场次效率'] = (df_movie['票房比'] / df_movie['场次比']).fillna(0)
|
275 |
+
|
276 |
+
avg_movie_time_efficiency = df_movie.groupby('时间点')[['座次效率', '场次效率']].mean().reset_index()
|
277 |
+
avg_movie_time_efficiency['时间点'] = avg_movie_time_efficiency['时间点'].apply(lambda t: t.strftime('%H:%M'))
|
278 |
+
|
279 |
+
source = avg_movie_time_efficiency.melt(id_vars=['时间点'], value_vars=['座次效率', '场次效率'],
|
280 |
+
var_name='效率类型', value_name='效率值')
|
281 |
+
chart = alt.Chart(source).mark_bar().encode(
|
282 |
+
x=alt.X('时间点:N', title='时间点', sort=None, axis=alt.Axis(labelAngle=-45)),
|
283 |
+
y=alt.Y('效率值:Q', title='平均效率'),
|
284 |
+
color='效率类型:N',
|
285 |
+
xOffset='效率类型:N',
|
286 |
+
tooltip=[alt.Tooltip('时间点:N'), alt.Tooltip('效率类型:N'), alt.Tooltip('效率值:Q', format='.2f')]
|
287 |
+
).properties(title=f'影片《{selected_movie}》各时间点平均效率分析 (对比全天)').interactive()
|
288 |
+
st.altair_chart(chart, use_container_width=True)
|
289 |
+
|
290 |
+
|
291 |
+
# --- REQUIREMENT 2: New function for windowed daily efficiency analysis ---
|
292 |
+
def plot_windowed_daily_efficiency(df, window_minutes):
|
293 |
+
df['时间点'] = df['放映时间'].apply(round_time_to_5min)
|
294 |
+
time_slots = sorted(df['时间点'].unique())
|
295 |
+
all_days = df['放映日期'].unique()
|
296 |
+
|
297 |
+
results = []
|
298 |
+
|
299 |
+
for center_time in time_slots:
|
300 |
+
center_dt = datetime.datetime.combine(datetime.date.today(), center_time)
|
301 |
+
start_dt = center_dt - datetime.timedelta(minutes=window_minutes)
|
302 |
+
end_dt = center_dt + datetime.timedelta(minutes=window_minutes)
|
303 |
+
|
304 |
+
daily_efficiencies = []
|
305 |
+
for day in all_days:
|
306 |
+
day_df = df[df['放映日期'] == day]
|
307 |
|
308 |
+
# Numerator: Center point's performance
|
309 |
+
center_df = day_df[day_df['时间点'] == center_time]
|
310 |
+
center_revenue = center_df['总收入'].sum()
|
311 |
+
center_seats = center_df['座位数'].sum()
|
312 |
+
center_sessions = len(center_df)
|
313 |
+
|
314 |
+
# Denominator: Window's performance
|
315 |
+
window_df = day_df[day_df['放映时间'].between(start_dt.time(), end_dt.time())]
|
316 |
+
window_revenue = window_df['总收入'].sum()
|
317 |
+
window_seats = window_df['座位数'].sum()
|
318 |
+
window_sessions = len(window_df)
|
319 |
+
|
320 |
+
if window_revenue > 0 and window_seats > 0 and window_sessions > 0:
|
321 |
+
票房比 = center_revenue / window_revenue
|
322 |
+
座次比 = center_seats / window_seats
|
323 |
+
场次比 = center_sessions / window_sessions
|
324 |
+
|
325 |
+
seat_efficiency = (票房比 / 座次比) if 座次比 > 0 else 0
|
326 |
+
session_efficiency = (票房比 / 场次比) if 场次比 > 0 else 0
|
327 |
+
daily_efficiencies.append({'seat': seat_efficiency, 'session': session_efficiency})
|
328 |
+
|
329 |
+
if daily_efficiencies:
|
330 |
+
avg_seat_eff = np.mean([d['seat'] for d in daily_efficiencies])
|
331 |
+
avg_session_eff = np.mean([d['session'] for d in daily_efficiencies])
|
332 |
+
results.append(
|
333 |
+
{'时间点': center_time.strftime('%H:%M'), '座次效率': avg_seat_eff, '场次效率': avg_session_eff})
|
334 |
+
|
335 |
+
if not results:
|
336 |
+
st.warning("没有足够的数据来计算分时间段的每日效率。")
|
337 |
+
return
|
338 |
+
|
339 |
+
results_df = pd.DataFrame(results)
|
340 |
+
source = results_df.melt(id_vars=['时间点'], value_vars=['座次效率', '场次效率'], var_name='效率类型',
|
341 |
+
value_name='效率值')
|
342 |
+
chart = alt.Chart(source).mark_bar().encode(
|
343 |
+
x=alt.X('时间点:N', sort=None, axis=alt.Axis(labelAngle=-45)),
|
344 |
+
y=alt.Y('效率值:Q', title=f'平均效率 (对比±{window_minutes}分钟窗口)'),
|
345 |
+
color='效率类型:N',
|
346 |
+
xOffset='效率类型:N',
|
347 |
+
tooltip=[alt.Tooltip('时间点:N'), alt.Tooltip('效率类型:N'), alt.Tooltip('效率值:Q', format='.2f')]
|
348 |
+
).properties(title=f'每日时间效率分析 (移动窗口: {window_minutes * 2}分钟)').interactive()
|
349 |
+
st.altair_chart(chart, use_container_width=True)
|
350 |
+
|
351 |
+
|
352 |
+
# --- REQUIREMENT 3: New function for windowed movie efficiency analysis ---
|
353 |
+
def plot_windowed_movie_efficiency(df, center_time, window_minutes):
|
354 |
+
df['时间点'] = df['放映时间'].apply(round_time_to_5min)
|
355 |
+
center_dt = datetime.datetime.combine(datetime.date.today(), center_time)
|
356 |
+
start_dt = center_dt - datetime.timedelta(minutes=window_minutes)
|
357 |
+
end_dt = center_dt + datetime.timedelta(minutes=window_minutes)
|
358 |
+
|
359 |
+
all_days = df['放映日期'].unique()
|
360 |
+
movie_list = df['影片名称_清理后'].unique()
|
361 |
+
results = []
|
362 |
+
|
363 |
+
for movie in movie_list:
|
364 |
+
daily_efficiencies = []
|
365 |
+
for day in all_days:
|
366 |
+
day_df = df[df['放映日期'] == day]
|
367 |
+
|
368 |
+
# Denominator: Window's performance on a specific day
|
369 |
+
window_df = day_df[day_df['放映时间'].between(start_dt.time(), end_dt.time())]
|
370 |
+
window_revenue = window_df['总收入'].sum()
|
371 |
+
window_seats = window_df['座位数'].sum()
|
372 |
+
window_sessions = len(window_df)
|
373 |
+
|
374 |
+
if window_revenue > 0 and window_seats > 0 and window_sessions > 0:
|
375 |
+
# Numerator: Movie's performance at the center point on that day
|
376 |
+
movie_center_df = day_df[(day_df['时间点'] == center_time) & (day_df['影片名称_清理后'] == movie)]
|
377 |
+
movie_center_revenue = movie_center_df['总收入'].sum()
|
378 |
+
movie_center_seats = movie_center_df['座位数'].sum()
|
379 |
+
movie_center_sessions = len(movie_center_df)
|
380 |
+
|
381 |
+
if movie_center_revenue > 0: # Only calculate if the movie had a show
|
382 |
+
票房比 = movie_center_revenue / window_revenue
|
383 |
+
座次比 = movie_center_seats / window_seats
|
384 |
+
场次比 = movie_center_sessions / window_sessions
|
385 |
+
|
386 |
+
seat_efficiency = (票房比 / 座次比) if 座次比 > 0 else 0
|
387 |
+
session_efficiency = (票房比 / 场次比) if 场次比 > 0 else 0
|
388 |
+
daily_efficiencies.append({'seat': seat_efficiency, 'session': session_efficiency})
|
389 |
+
|
390 |
+
if daily_efficiencies:
|
391 |
+
avg_seat_eff = np.mean([d['seat'] for d in daily_efficiencies])
|
392 |
+
avg_session_eff = np.mean([d['session'] for d in daily_efficiencies])
|
393 |
+
results.append({'影片': movie, '座次效率': avg_seat_eff, '场次效率': avg_session_eff})
|
394 |
+
|
395 |
+
if not results:
|
396 |
+
st.warning(
|
397 |
+
f"在 {start_dt.time().strftime('%H:%M')} - {end_dt.time().strftime('%H:%M')} 时间段内没有足够的数据进行单片效率分析。")
|
398 |
+
return
|
399 |
+
|
400 |
+
results_df = pd.DataFrame(results).sort_values(by='座次效率', ascending=False)
|
401 |
+
source = results_df.melt(id_vars=['影片'], value_vars=['座次效率', '场次效率'], var_name='效率类型',
|
402 |
+
value_name='效率值')
|
403 |
+
chart = alt.Chart(source).mark_bar().encode(
|
404 |
+
x=alt.X('效率值:Q'),
|
405 |
+
y=alt.Y('影片:N', sort='-x'),
|
406 |
+
color='效率类型:N',
|
407 |
+
tooltip=[alt.Tooltip('影片:N'), alt.Tooltip('效率类型:N'), alt.Tooltip('效率值:Q', format='.2f')]
|
408 |
+
).properties(
|
409 |
+
title=f"时间段 {start_dt.time().strftime('%H:%M')}-{end_dt.time().strftime('%H:%M')} 内单片平均效率").interactive()
|
410 |
+
st.altair_chart(chart, use_container_width=True)
|
411 |
+
|
412 |
+
|
413 |
+
# --- TMS Server Movie Content Inquiry ---
|
414 |
+
@st.cache_data(show_spinner=False)
|
415 |
+
def fetch_and_process_server_movies(priority_movie_titles=None):
|
416 |
+
if priority_movie_titles is None:
|
417 |
+
priority_movie_titles = []
|
418 |
+
# (The rest of the TMS function remains unchanged)
|
419 |
+
# 1. Get Token
|
420 |
+
try:
|
421 |
+
token_headers = {
|
422 |
+
'Host': 'oa.hengdianfilm.com:7080', 'Content-Type': 'application/json',
|
423 |
+
'Origin': 'http://115.239.253.233:7080', 'Connection': 'keep-alive',
|
424 |
+
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
425 |
+
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1',
|
426 |
+
'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
|
427 |
+
}
|
428 |
+
token_json_data = {'appId': 'hd', 'appSecret': 'ad761f8578cc6170', 'timeStamp': int(time.time() * 1000)}
|
429 |
+
token_url = 'http://oa.hengdianfilm.com:7080/cinema-api/admin/generateToken?token=hd&murl=?token=hd&murl=ticket=-1495916529737643774'
|
430 |
+
response = requests.post(token_url, headers=token_headers, json=token_json_data, timeout=10)
|
431 |
+
response.raise_for_status()
|
432 |
+
token_data = response.json()
|
433 |
+
if token_data.get('error_code') != '0000':
|
434 |
+
st.error(f"获取Token失败: {token_data.get('error_desc', '未知错误')}")
|
435 |
+
return {}, []
|
436 |
+
auth_token = token_data['param']
|
437 |
+
except requests.exceptions.RequestException as e:
|
438 |
+
st.error(f"网络请求错误: {e}")
|
439 |
+
return {}, []
|
440 |
except Exception as e:
|
441 |
+
st.error(f"获取Token时发生未知错误: {e}")
|
442 |
+
return {}, []
|
443 |
+
|
444 |
+
# 2. Fetch movie list (with pagination and delay)
|
445 |
+
all_movies = []
|
446 |
+
page_index = 1
|
447 |
+
while True:
|
448 |
+
try:
|
449 |
+
list_headers = {
|
450 |
+
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
451 |
+
'Content-Type': 'application/json; charset=UTF-8',
|
452 |
+
'Origin': 'http://115.239.253.233:7080', 'Proxy-Connection': 'keep-alive', 'Token': auth_token,
|
453 |
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
|
454 |
+
'X-SESSIONID': 'PQ0J3K85GJEDVYIGZE1KEG1K80USDAP4',
|
455 |
+
}
|
456 |
+
list_params = {'token': 'hd', 'murl': 'ContentMovie'}
|
457 |
+
list_json_data = {'THEATER_ID': 38205954, 'SOURCE': 'SERVER', 'ASSERT_TYPE': 2, 'PAGE_CAPACITY': 20,
|
458 |
+
'PAGE_INDEX': page_index}
|
459 |
+
list_url = 'http://oa.hengdianfilm.com:7080/cinema-api/cinema/server/dcp/list'
|
460 |
+
response = requests.post(list_url, params=list_params, headers=list_headers, json=list_json_data,
|
461 |
+
verify=False)
|
462 |
+
response.raise_for_status()
|
463 |
+
movie_data = response.json()
|
464 |
+
|
465 |
+
if movie_data.get("RSPCD") != "000000":
|
466 |
+
st.error(f"获取影片列表失败: {movie_data.get('RSPMSG', '未知错误')}")
|
467 |
+
return {}, []
|
468 |
+
|
469 |
+
body = movie_data.get("BODY", {})
|
470 |
+
movies_on_page = body.get("LIST", [])
|
471 |
+
if not movies_on_page: break
|
472 |
+
all_movies.extend(movies_on_page)
|
473 |
+
if len(all_movies) >= body.get("COUNT", 0): break
|
474 |
+
page_index += 1
|
475 |
+
time.sleep(1)
|
476 |
+
except requests.exceptions.RequestException as e:
|
477 |
+
st.error(f"网络请求错误: {e}")
|
478 |
+
return {}, []
|
479 |
+
except Exception as e:
|
480 |
+
st.error(f"获取影片列表时发生未知错误: {e}")
|
481 |
+
return {}, []
|
482 |
+
|
483 |
+
# 3. Process data
|
484 |
+
movie_details = {m['CONTENT_NAME']: {'assert_name': m.get('ASSERT_NAME'),
|
485 |
+
'halls': sorted([h.get('HALL_NAME') for h in m.get('HALL_INFO', [])]),
|
486 |
+
'play_time': m.get('PLAY_TIME')} for m in all_movies if m.get('CONTENT_NAME')}
|
487 |
+
by_hall = defaultdict(list)
|
488 |
+
for name, details in movie_details.items():
|
489 |
+
for hall in details['halls']: by_hall[hall].append({'content_name': name, 'details': details})
|
490 |
+
for hall in by_hall: by_hall[hall].sort(
|
491 |
+
key=lambda item: (item['details']['assert_name'] is None or item['details']['assert_name'] == '',
|
492 |
+
item['details']['assert_name'] or item['content_name']))
|
493 |
+
|
494 |
+
view2_list = [
|
495 |
+
{'assert_name': d['assert_name'], 'content_name': name, 'halls': d['halls'], 'play_time': d['play_time']} for
|
496 |
+
name, d in movie_details.items() if d.get('assert_name')]
|
497 |
+
priority_list = [item for item in view2_list if any(p in item['assert_name'] for p in priority_movie_titles)]
|
498 |
+
other_list = [item for item in view2_list if item not in priority_list]
|
499 |
+
priority_list.sort(key=lambda x: x['assert_name']);
|
500 |
+
other_list.sort(key=lambda x: x['assert_name'])
|
501 |
+
|
502 |
+
return dict(sorted(by_hall.items())), priority_list + other_list
|
503 |
+
|
504 |
+
|
505 |
+
# --- Streamlit Main UI ---
|
506 |
+
st.title('影城排片效率与内容分析工具')
|
507 |
+
st.write("上传 `影片映出日累计报表.xlsx` 进行效率分析,或点击下方按钮查询 TMS 服务器影片内容。")
|
508 |
+
|
509 |
+
uploaded_file = st.file_uploader("请在此处上传 Excel 文件", type=['xlsx', 'xls', 'csv'])
|
510 |
+
query_tms_for_location = st.checkbox("查询 TMS 找影片所在影厅")
|
511 |
+
|
512 |
+
if uploaded_file is not None:
|
513 |
+
try:
|
514 |
+
df = pd.read_excel(uploaded_file, skiprows=3, header=None)
|
515 |
+
df['场次'] = 1
|
516 |
+
df.rename(columns={0: '影片名称', 1: '放映日期', 2: '放映时间', 5: '总人次', 6: '总收入', 7: '座位数'},
|
517 |
+
inplace=True)
|
518 |
+
required_cols = ['影片名称', '放映日期', '放映时间', '座位数', '总收入', '总人次', '场次']
|
519 |
+
df = df[required_cols]
|
520 |
+
|
521 |
+
df.dropna(subset=['影片名称', '放映日期', '放映时间'], inplace=True)
|
522 |
+
df['放映日期'] = pd.to_datetime(df['放映日期'], errors='coerce').dt.date
|
523 |
+
df.dropna(subset=['放映日期'], inplace=True)
|
524 |
+
|
525 |
+
for col in ['座位数', '总收入', '总人次']:
|
526 |
+
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
|
527 |
+
|
528 |
+
df['放映时间'] = pd.to_datetime(df['放映时间'], format='%H:%M:%S', errors='coerce').dt.time
|
529 |
+
df.dropna(subset=['放映时间'], inplace=True)
|
530 |
+
df['影片名称_清理后'] = df['影片名称'].apply(clean_movie_title)
|
531 |
+
|
532 |
+
st.toast("文件上传成功,效率分析已生成!", icon="🎉")
|
533 |
+
|
534 |
+
format_config = {'座位数': '{:,.0f}', '场次': '{:,.0f}', '人次': '{:,.0f}', '票房': '{:,.2f}', '均价': '{:.2f}',
|
535 |
+
'座次比': '{:.2%}', '场次比': '{:.2%}', '票房比': '{:.2%}', '座次效率': '{:.2f}',
|
536 |
+
'场次效率': '{:.2f}'}
|
537 |
+
|
538 |
+
full_day_analysis = process_and_analyze_data(df.copy())
|
539 |
+
prime_time_analysis = process_and_analyze_data(
|
540 |
+
df[df['放映时间'].between(datetime.time(14, 0), datetime.time(21, 0))].copy())
|
541 |
+
|
542 |
+
if query_tms_for_location:
|
543 |
+
# ... (TMS logic remains unchanged)
|
544 |
+
pass
|
545 |
+
|
546 |
+
st.markdown("### 全天排片效率分析")
|
547 |
+
if not full_day_analysis.empty:
|
548 |
+
st.dataframe(full_day_analysis.style.format(format_config), use_container_width=True, hide_index=True)
|
549 |
+
|
550 |
+
st.markdown("#### 黄金时段排片效率分析 (14:00-21:00)")
|
551 |
+
if not prime_time_analysis.empty:
|
552 |
+
st.dataframe(prime_time_analysis.style.format(format_config), use_container_width=True, hide_index=True)
|
553 |
+
|
554 |
+
if not full_day_analysis.empty:
|
555 |
+
st.markdown("##### 复制当日排片列表")
|
556 |
+
movie_titles = full_day_analysis['影片'].tolist()
|
557 |
+
formatted_titles = ''.join([f'《{title}》' for title in movie_titles])
|
558 |
+
st.code(formatted_titles, language='text')
|
559 |
+
|
560 |
+
if not df.empty:
|
561 |
+
with st.expander("影城每日票房表现", expanded=True):
|
562 |
+
movie_options = ['全部影片'] + full_day_analysis['影片'].unique().tolist()
|
563 |
+
selected_movie_for_chart = st.selectbox('选择影片查看其每日票房', options=movie_options,
|
564 |
+
key='daily_box_office_selector')
|
565 |
+
daily_chart = plot_daily_box_office(df.copy(), selected_movie_for_chart)
|
566 |
+
if daily_chart:
|
567 |
+
st.altair_chart(daily_chart, use_container_width=True)
|
568 |
+
|
569 |
+
# --- UI CHANGE FOR REQUIREMENT 1 ---
|
570 |
+
st.markdown("---")
|
571 |
+
plot_daily_box_office_by_time(df.copy(), selected_movie_for_chart)
|
572 |
+
|
573 |
+
# --- UI CHANGE FOR REQUIREMENTS 2 & 3 ---
|
574 |
+
with st.expander("每日时间效率分析", expanded=False):
|
575 |
+
tab1, tab2, tab3, tab4 = st.tabs([
|
576 |
+
"每日效率(对比全天)",
|
577 |
+
"单片效率(对比全天)",
|
578 |
+
"每日效率(分时间段)",
|
579 |
+
"单片效率(分时间段)"
|
580 |
+
])
|
581 |
+
|
582 |
+
with tab1:
|
583 |
+
st.write("分析所有影片在各时间点(5分钟聚合)的平均效率。效率值通过对比 **全天** 的总表现得出。")
|
584 |
+
plot_time_efficiency_analysis(df.copy())
|
585 |
+
|
586 |
+
with tab2:
|
587 |
+
st.write("选择一部影片,查看其在各时间点的平均效率。效率值通过对比 **全天** 的总表现得出。")
|
588 |
+
movie_options_for_time = ['全部影片'] + full_day_analysis['影片'].unique().tolist()
|
589 |
+
selected_movie_for_time_chart = st.selectbox('选择影片', options=movie_options_for_time,
|
590 |
+
key='movie_time_selector')
|
591 |
+
plot_movie_time_efficiency_analysis(df.copy(), selected_movie_for_time_chart)
|
592 |
+
|
593 |
+
with tab3:
|
594 |
+
st.write("分析每个时间点的效率,效率值通过对比该时间点 **周边指定时间窗口** 的总表现得出。")
|
595 |
+
window_daily = st.number_input("时间窗口(前后各x分钟)", min_value=5, value=20, step=5,
|
596 |
+
key='daily_window')
|
597 |
+
plot_windowed_daily_efficiency(df.copy(), window_daily)
|
598 |
+
|
599 |
+
with tab4:
|
600 |
+
st.write(
|
601 |
+
"在指定时间窗口内,分析各影片的效率。效率值通过对比影片在 **中心时间点** 的表现与 **整个窗口** 的总表现得出。")
|
602 |
+
col1, col2 = st.columns(2)
|
603 |
+
with col1:
|
604 |
+
center_time_movie = st.time_input("中心时间点", value=datetime.time(19, 30),
|
605 |
+
step=datetime.timedelta(minutes=5), key='movie_time_center')
|
606 |
+
with col2:
|
607 |
+
window_movie = st.number_input("时间窗口(前后各x分钟)", min_value=5, value=20, step=5,
|
608 |
+
key='movie_window')
|
609 |
+
plot_windowed_movie_efficiency(df.copy(), center_time_movie, window_movie)
|
610 |
+
|
611 |
+
except Exception as e:
|
612 |
+
st.error(f"处理文件时出错: {e}")
|
613 |
+
st.error("请检查您的 Excel 文件格式是否正确,特别是日期和时间列。")
|
614 |
+
|
615 |
+
# (TMS UI part remains unchanged)
|
616 |
+
st.divider()
|
617 |
+
st.markdown("### TMS 服务器影片内容查询")
|
618 |
+
if st.button('点击查询 TMS 服务器'):
|
619 |
+
with st.spinner("正在从 TMS 服务器获取数据中..."):
|
620 |
+
try:
|
621 |
+
halls_data, movie_list_sorted = fetch_and_process_server_movies()
|
622 |
+
st.toast("TMS 服务器数据获取成功!", icon="🎉")
|
623 |
+
if halls_data or movie_list_sorted:
|
624 |
+
st.markdown("#### 按影片查看所在影厅")
|
625 |
+
view2_data = [{'影片名称': item['assert_name'],
|
626 |
+
'所在影厅': " ".join(sorted([get_circled_number(h) for h in item['halls']])),
|
627 |
+
'文件名': item['content_name'], '时长': format_play_time(item['play_time'])} for item in
|
628 |
+
movie_list_sorted]
|
629 |
+
df_view2 = pd.DataFrame(view2_data)
|
630 |
+
st.dataframe(df_view2, hide_index=True, use_container_width=True)
|
631 |
+
|
632 |
+
st.markdown("#### 按影厅查看影片内容")
|
633 |
+
hall_tabs = st.tabs(list(halls_data.keys()))
|
634 |
+
for tab, hall_name in zip(hall_tabs, halls_data.keys()):
|
635 |
+
with tab:
|
636 |
+
view1_data_for_tab = [{'影片名称': item['details']['assert_name'],
|
637 |
+
'所在影厅': " ".join(
|
638 |
+
sorted([get_circled_number(h) for h in item['details']['halls']])),
|
639 |
+
'文件名': item['content_name'],
|
640 |
+
'时长': format_play_time(item['details']['play_time'])} for item in
|
641 |
+
halls_data[hall_name]]
|
642 |
+
df_view1_tab = pd.DataFrame(view1_data_for_tab)
|
643 |
+
st.dataframe(df_view1_tab, hide_index=True, use_container_width=True)
|
644 |
+
except Exception as e:
|
645 |
+
st.error(f"查询服务器时出错: {e}")
|