Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,6 +6,7 @@ import time
|
|
| 6 |
from collections import defaultdict
|
| 7 |
import json
|
| 8 |
import os
|
|
|
|
| 9 |
from datetime import datetime, timedelta, time as dt_time
|
| 10 |
import io
|
| 11 |
import warnings
|
|
@@ -22,6 +23,7 @@ from itertools import combinations
|
|
| 22 |
from dotenv import load_dotenv
|
| 23 |
load_dotenv() # 加载本地 .env 文件
|
| 24 |
|
|
|
|
| 25 |
# --- 全局配置和常量 ---
|
| 26 |
TOKEN_FILE = 'token_data.json'
|
| 27 |
# --- 环境变量获取 (替代硬编码) ---
|
|
@@ -1326,79 +1328,118 @@ def generate_schedule_check_logs(schedule_list, date_str):
|
|
| 1326 |
def check_tms_file_availability(schedule_list, tms_data, date_str):
|
| 1327 |
"""
|
| 1328 |
对比排片表和TMS数据,检查影厅是否缺失对应的影片文件
|
|
|
|
| 1329 |
"""
|
| 1330 |
if not schedule_list:
|
| 1331 |
-
return
|
| 1332 |
|
| 1333 |
if not tms_data:
|
| 1334 |
-
return
|
| 1335 |
-
|
| 1336 |
-
#
|
| 1337 |
-
|
| 1338 |
-
|
| 1339 |
-
|
| 1340 |
-
|
| 1341 |
-
|
| 1342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1343 |
nums = re.findall(r'\d+', str(name))
|
| 1344 |
return nums[0] if nums else str(name)
|
| 1345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
for hall_name, movies in tms_data.items():
|
| 1347 |
-
hall_key =
|
| 1348 |
for movie in movies:
|
| 1349 |
-
# 收集
|
| 1350 |
-
# 转为大写方便不区分大小写匹配
|
| 1351 |
if movie.get('details', {}).get('assert_name'):
|
| 1352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1353 |
if movie.get('content_name'):
|
| 1354 |
-
tms_map[hall_key].
|
| 1355 |
|
| 1356 |
# 2. 遍历排片数据进行检查
|
| 1357 |
-
|
| 1358 |
-
# 用于缓存已经检查过的 (影厅, 影片) 组合,避免重复报错
|
| 1359 |
checked_combinations = set()
|
| 1360 |
|
| 1361 |
for item in schedule_list:
|
| 1362 |
-
# 排片数据字段可能不同,做一下兼容
|
| 1363 |
hall_raw = item.get('hallName') or item.get('Hall')
|
| 1364 |
movie_raw = item.get('movieName') or item.get('Movie')
|
| 1365 |
|
| 1366 |
if not hall_raw or not movie_raw:
|
| 1367 |
continue
|
| 1368 |
|
| 1369 |
-
|
|
|
|
|
|
|
| 1370 |
|
| 1371 |
-
#
|
| 1372 |
-
|
| 1373 |
-
movie_clean = clean_movie_title(movie_raw).upper()
|
| 1374 |
|
| 1375 |
-
#
|
| 1376 |
-
combo_key = (hall_num,
|
| 1377 |
if combo_key in checked_combinations:
|
| 1378 |
continue
|
| 1379 |
checked_combinations.add(combo_key)
|
| 1380 |
|
| 1381 |
-
#
|
| 1382 |
if hall_num not in tms_map:
|
| 1383 |
-
#
|
| 1384 |
-
# missing_files_log.append(f"⚠️ 影厅异常:TMS 中未找到【{hall_raw}】的数据,无法检查该厅《{movie_raw}》。")
|
| 1385 |
continue
|
| 1386 |
|
| 1387 |
-
#
|
| 1388 |
-
#
|
|
|
|
|
|
|
| 1389 |
has_file = False
|
| 1390 |
tms_files = tms_map[hall_num]
|
| 1391 |
|
|
|
|
|
|
|
| 1392 |
for tms_file in tms_files:
|
| 1393 |
-
if
|
| 1394 |
has_file = True
|
| 1395 |
break
|
| 1396 |
|
| 1397 |
if not has_file:
|
| 1398 |
-
|
| 1399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1400 |
|
| 1401 |
-
return
|
| 1402 |
|
| 1403 |
# --- 5. UI 渲染与交互逻辑 ---
|
| 1404 |
|
|
@@ -1760,8 +1801,7 @@ def main():
|
|
| 1760 |
check_date_str = check_date.strftime('%Y-%m-%d')
|
| 1761 |
|
| 1762 |
with st.spinner(f"正在获取 {check_date_str} 的排片数据并连接 TMS 服务器..."):
|
| 1763 |
-
# 1. 获取次日排片
|
| 1764 |
-
# 注意:为了确保数据最新,这里重新快速获取一次原始数据
|
| 1765 |
schedule_data, _ = get_api_data_with_token_management(check_date_str)
|
| 1766 |
|
| 1767 |
if not schedule_data:
|
|
@@ -1769,24 +1809,23 @@ def main():
|
|
| 1769 |
else:
|
| 1770 |
try:
|
| 1771 |
# 2. 获取 TMS 数据
|
| 1772 |
-
# 提取排片中出现的所有影片名作为优先查询关键词,加快 TMS 搜索速度 (虽然后台是全量拉取)
|
| 1773 |
df_sched = pd.DataFrame(schedule_data)
|
| 1774 |
priority_titles = df_sched[
|
| 1775 |
'movieName'].unique().tolist() if 'movieName' in df_sched.columns else []
|
| 1776 |
|
| 1777 |
tms_hall_data, _ = fetch_and_process_server_movies(priority_titles)
|
| 1778 |
|
| 1779 |
-
# 3. 执行比对
|
| 1780 |
-
|
| 1781 |
-
|
| 1782 |
|
| 1783 |
-
if
|
| 1784 |
st.success(
|
| 1785 |
f"✅ 核对完成:{check_date_str} 所有排映影片在对应影厅服务器中均存在关联文件。")
|
| 1786 |
else:
|
| 1787 |
-
st.
|
| 1788 |
-
|
| 1789 |
-
|
| 1790 |
|
| 1791 |
except Exception as e:
|
| 1792 |
st.error(f"核对过程中发生错误: {e}")
|
|
|
|
| 6 |
from collections import defaultdict
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
+
import re
|
| 10 |
from datetime import datetime, timedelta, time as dt_time
|
| 11 |
import io
|
| 12 |
import warnings
|
|
|
|
| 23 |
from dotenv import load_dotenv
|
| 24 |
load_dotenv() # 加载本地 .env 文件
|
| 25 |
|
| 26 |
+
|
| 27 |
# --- 全局配置和常量 ---
|
| 28 |
TOKEN_FILE = 'token_data.json'
|
| 29 |
# --- 环境变量获取 (替代硬编码) ---
|
|
|
|
| 1328 |
def check_tms_file_availability(schedule_list, tms_data, date_str):
|
| 1329 |
"""
|
| 1330 |
对比排片表和TMS数据,检查影厅是否缺失对应的影片文件
|
| 1331 |
+
优化:仅匹配核心片名(去除版本后缀),优化影厅名显示,合并日志输出
|
| 1332 |
"""
|
| 1333 |
if not schedule_list:
|
| 1334 |
+
return "未获取到排片数据,无法检查。"
|
| 1335 |
|
| 1336 |
if not tms_data:
|
| 1337 |
+
return "未获取到 TMS 数据,无法检查。"
|
| 1338 |
+
|
| 1339 |
+
# --- 内部辅助函数 ---
|
| 1340 |
+
def get_core_movie_name(raw_name):
|
| 1341 |
+
"""
|
| 1342 |
+
获取核心片名用于匹配:
|
| 1343 |
+
1. 先执行标准的 clean_movie_title (统一命名)
|
| 1344 |
+
2. 再去除所有括号及括号内的内容 (去除版本/制式信息)
|
| 1345 |
+
例如:'疯狂动物城2(数字3D)' -> '疯狂动物城2'
|
| 1346 |
+
"""
|
| 1347 |
+
# 1. 基础清洗 (利用现有的逻辑处理中英文/特殊后缀)
|
| 1348 |
+
# 注意:这里我们不传入 canonical_names,只做规则清洗
|
| 1349 |
+
name = clean_movie_title(raw_name)
|
| 1350 |
+
# 2. 正则去除中文全角括号及内容 (...)
|
| 1351 |
+
name = re.sub(r'(.*?)', '', name)
|
| 1352 |
+
# 3. 正则去除英文半角括号及内容 (...)
|
| 1353 |
+
name = re.sub(r'\(.*?\)', '', name)
|
| 1354 |
+
return name.strip()
|
| 1355 |
+
|
| 1356 |
+
def clean_hall_display_name(raw_name):
|
| 1357 |
+
"""去除影厅名两端多余的 【】 [] 符号"""
|
| 1358 |
+
return str(raw_name).strip('【】[] ')
|
| 1359 |
+
|
| 1360 |
+
def get_hall_key_num(name):
|
| 1361 |
+
"""提取影厅数字ID用于数据匹配 (如 '1号厅' -> '1')"""
|
| 1362 |
nums = re.findall(r'\d+', str(name))
|
| 1363 |
return nums[0] if nums else str(name)
|
| 1364 |
|
| 1365 |
+
# ------------------
|
| 1366 |
+
|
| 1367 |
+
# 1. 预处理 TMS 数据
|
| 1368 |
+
# 结构: {'1': ['Zootopia2', '疯狂动物城2', ...], ...}
|
| 1369 |
+
tms_map = defaultdict(set) # 使用 set 提高查找效率
|
| 1370 |
+
|
| 1371 |
for hall_name, movies in tms_data.items():
|
| 1372 |
+
hall_key = get_hall_key_num(hall_name)
|
| 1373 |
for movie in movies:
|
| 1374 |
+
# 收集 Assert Name (显示名)
|
|
|
|
| 1375 |
if movie.get('details', {}).get('assert_name'):
|
| 1376 |
+
# 同样对 TMS 里的名字取核心名,提高匹配率
|
| 1377 |
+
core_tms_name = get_core_movie_name(str(movie['details']['assert_name']))
|
| 1378 |
+
tms_map[hall_key].add(core_tms_name.upper())
|
| 1379 |
+
# 保留原始 Assert Name 用于兜底匹配
|
| 1380 |
+
tms_map[hall_key].add(str(movie['details']['assert_name']).upper())
|
| 1381 |
+
|
| 1382 |
+
# 收集 Content Name (文件名/UUID)
|
| 1383 |
if movie.get('content_name'):
|
| 1384 |
+
tms_map[hall_key].add(str(movie['content_name']).upper())
|
| 1385 |
|
| 1386 |
# 2. 遍历排片数据进行检查
|
| 1387 |
+
missing_logs = []
|
|
|
|
| 1388 |
checked_combinations = set()
|
| 1389 |
|
| 1390 |
for item in schedule_list:
|
|
|
|
| 1391 |
hall_raw = item.get('hallName') or item.get('Hall')
|
| 1392 |
movie_raw = item.get('movieName') or item.get('Movie')
|
| 1393 |
|
| 1394 |
if not hall_raw or not movie_raw:
|
| 1395 |
continue
|
| 1396 |
|
| 1397 |
+
# 准备数据
|
| 1398 |
+
hall_num = get_hall_key_num(hall_raw)
|
| 1399 |
+
hall_display = clean_hall_display_name(hall_raw) # 清洗后的影厅名
|
| 1400 |
|
| 1401 |
+
# 获取排片的核心片名 (去掉版本后缀)
|
| 1402 |
+
target_movie_core = get_core_movie_name(movie_raw).upper()
|
|
|
|
| 1403 |
|
| 1404 |
+
# 组合键去重 (同一厅同一部片只报一次)
|
| 1405 |
+
combo_key = (hall_num, target_movie_core)
|
| 1406 |
if combo_key in checked_combinations:
|
| 1407 |
continue
|
| 1408 |
checked_combinations.add(combo_key)
|
| 1409 |
|
| 1410 |
+
# 检查逻辑
|
| 1411 |
if hall_num not in tms_map:
|
| 1412 |
+
# 找不到影厅数据暂不报错,可能是未映射或设备离线,避免刷屏
|
|
|
|
| 1413 |
continue
|
| 1414 |
|
| 1415 |
+
# 核心匹配:检查 TMS 集合中是否包含核心片名
|
| 1416 |
+
# 方式A:精确匹配核心名 (推荐,最准)
|
| 1417 |
+
# 方式B:模糊包含 (target in tms_file)
|
| 1418 |
+
|
| 1419 |
has_file = False
|
| 1420 |
tms_files = tms_map[hall_num]
|
| 1421 |
|
| 1422 |
+
# 策略:只要 TMS 中有一个文件名 包含 我们的核心排片名,就视为有片
|
| 1423 |
+
# 例如:排片 core='疯狂动物城2',TMS='疯狂动物城2_IMAX' -> 匹配成功
|
| 1424 |
for tms_file in tms_files:
|
| 1425 |
+
if target_movie_core in tms_file:
|
| 1426 |
has_file = True
|
| 1427 |
break
|
| 1428 |
|
| 1429 |
if not has_file:
|
| 1430 |
+
# 记录日志,使用清洗后的影厅名和排片原名
|
| 1431 |
+
missing_logs.append(f"【{hall_display}】排映《{movie_raw}》,但服务器未检测到包含“{target_movie_core}”的文件。")
|
| 1432 |
+
|
| 1433 |
+
# 3. 格式化输出
|
| 1434 |
+
if not missing_logs:
|
| 1435 |
+
return None # 返回 None 表示一切正常
|
| 1436 |
+
|
| 1437 |
+
# 生成带编号的字符串
|
| 1438 |
+
formatted_output = []
|
| 1439 |
+
for idx, log in enumerate(missing_logs, 1):
|
| 1440 |
+
formatted_output.append(f"{idx}. ❌ 缺片警告:{log}")
|
| 1441 |
|
| 1442 |
+
return "\n".join(formatted_output)
|
| 1443 |
|
| 1444 |
# --- 5. UI 渲染与交互逻辑 ---
|
| 1445 |
|
|
|
|
| 1801 |
check_date_str = check_date.strftime('%Y-%m-%d')
|
| 1802 |
|
| 1803 |
with st.spinner(f"正在获取 {check_date_str} 的排片数据并连接 TMS 服务器..."):
|
| 1804 |
+
# 1. 获取次日排片
|
|
|
|
| 1805 |
schedule_data, _ = get_api_data_with_token_management(check_date_str)
|
| 1806 |
|
| 1807 |
if not schedule_data:
|
|
|
|
| 1809 |
else:
|
| 1810 |
try:
|
| 1811 |
# 2. 获取 TMS 数据
|
|
|
|
| 1812 |
df_sched = pd.DataFrame(schedule_data)
|
| 1813 |
priority_titles = df_sched[
|
| 1814 |
'movieName'].unique().tolist() if 'movieName' in df_sched.columns else []
|
| 1815 |
|
| 1816 |
tms_hall_data, _ = fetch_and_process_server_movies(priority_titles)
|
| 1817 |
|
| 1818 |
+
# 3. 执行比对 (使用新函数)
|
| 1819 |
+
logs_text = check_tms_file_availability(schedule_data, tms_hall_data,
|
| 1820 |
+
check_date_str)
|
| 1821 |
|
| 1822 |
+
if logs_text is None:
|
| 1823 |
st.success(
|
| 1824 |
f"✅ 核对完成:{check_date_str} 所有排映影片在对应影厅服务器中均存在关联文件。")
|
| 1825 |
else:
|
| 1826 |
+
st.warning("⚠️ 发现潜在缺片风险!请检查以下影厅服务器:")
|
| 1827 |
+
# 这里使用 st.code 展示多行带编号的文本
|
| 1828 |
+
st.code(logs_text, language="text")
|
| 1829 |
|
| 1830 |
except Exception as e:
|
| 1831 |
st.error(f"核对过程中发生错误: {e}")
|