# coding=utf-8 import os import time CAIHUI_DATABASE = { 'name': os.environ.get('DISH_DB_NAME', 'goods'), 'host': os.environ.get('CARC_CRAWLER_DB_HOST', '127.0.0.1'), 'port': int(os.environ.get('CARC_CRAWLER_DB_PORT', 3306)), 'user': os.environ.get('CARC_CRAWLER_DB_USER', 'root'), 'password': os.environ.get('CARC_CRAWLER_DB_PASSWORD', '123456'), } # SQLite 本地临时缓存(识别读此库;变更后从 MySQL 拉取刷新) DISH_SQLITE_PATH = os.environ.get( 'DISH_SQLITE_PATH', os.path.join(os.path.dirname(__file__), 'dish_templates.db'), ) # 本机基地编码(学菜写入 source_base_id;结果 API 的 base_id 默认也用它) # 建议填管理系统返回的 code,例如「上海路基地-1788059884828-e55vu」 DISH_BASE_CODE = os.environ.get('DISH_BASE_CODE', 'BASE_LOCAL_01') DISH_BASE_NAME = os.environ.get('DISH_BASE_NAME', '奥体基地') DISH_BASE_REGION = os.environ.get('DISH_BASE_REGION', '') # 管理系统:公开接口(X-API-Key) # 完整 Key 见 docs/第三方接口文档_mgmt.md(含 dish_ 前缀) DISH_MGMT_API_ORIGIN = os.environ.get( 'DISH_MGMT_API_ORIGIN', 'https://cpsb.ruimeida.cc', ).rstrip('/') DISH_MGMT_API_URL = os.environ.get( 'DISH_MGMT_API_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/bases', ) DISH_MGMT_RESULT_API_URL = os.environ.get( 'DISH_MGMT_RESULT_API_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/recognition-orders', ) DISH_MGMT_UNRECOGNIZED_API_URL = os.environ.get( 'DISH_MGMT_UNRECOGNIZED_API_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/unrecognized-samples', ) DISH_MGMT_MEAL_API_URL = os.environ.get( 'DISH_MGMT_MEAL_API_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/meal-schedules/today', ) DISH_MGMT_DEVICE_STATUS_URL = os.environ.get( 'DISH_MGMT_DEVICE_STATUS_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/devices/status', ) DISH_MGMT_DISHES_API_URL = os.environ.get( 'DISH_MGMT_DISHES_API_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/dishes', ) DISH_MGMT_DISH_LEARNING_STATUS_URL = os.environ.get( 'DISH_MGMT_DISH_LEARNING_STATUS_URL', f'{DISH_MGMT_API_ORIGIN}/api/public/dishes/learning-status', ) DISH_MGMT_API_KEY = os.environ.get( 'DISH_MGMT_API_KEY', 'dish_a4f7a1287198179fdc6a6571aa14064c2afc6338c6730e2b668ac5be15bdf769', ) DISH_MGMT_API_TIMEOUT = float(os.environ.get('DISH_MGMT_API_TIMEOUT', '10')) DISH_MGMT_SYNC_ON_START = os.environ.get('DISH_MGMT_SYNC_ON_START', '1') == '1' DISH_MGMT_MEAL_ON_START = os.environ.get('DISH_MGMT_MEAL_ON_START', '1') == '1' # 本机设备(上报设备状态) DISH_DEVICE_CODE = os.environ.get('DISH_DEVICE_CODE', '') or os.environ.get('DISH_BASE_CODE', DISH_BASE_CODE) DISH_DEVICE_NAME = os.environ.get('DISH_DEVICE_NAME', '') or os.environ.get('DISH_BASE_NAME', DISH_BASE_NAME) DISH_DEVICE_STATUS_INTERVAL = int(os.environ.get('DISH_DEVICE_STATUS_INTERVAL', '60')) # 秒 DISH_DEVICE_STATUS_ENABLED = os.environ.get('DISH_DEVICE_STATUS_ENABLED', '1') == '1' # 启动时是否从 MySQL 拉取;后台定时同步间隔(秒,含上传队列 flush + 拉取) DISH_SYNC_ON_START = os.environ.get('DISH_SYNC_ON_START', '1') == '1' DISH_SYNC_INTERVAL = int(os.environ.get('DISH_SYNC_INTERVAL', 300)) # 默认 5 分钟 # 识别框是否显示「似xx 检xx」(默认关;需要时 set DISH_SHOW_BOX_SCORES=1) DISH_SHOW_BOX_SCORES = os.environ.get('DISH_SHOW_BOX_SCORES', '0') == '1' # 当前餐段匹配:有当日排餐时仅在「今日当前餐段」菜品中比对,未命中直接未知(不回退大库) # 1) 最高分 ≥ 0.80 → 认定;2) 未达 0.80 但最高分 > 0.75 → 仍认定;否则未知 DISH_MEAL_SMALL_LIB_THRESHOLD = float(os.environ.get('DISH_MEAL_SMALL_LIB_THRESHOLD', '0.82')) DISH_MEAL_SMALL_LIB_SOFT_THRESHOLD = float(os.environ.get('DISH_MEAL_SMALL_LIB_SOFT_THRESHOLD', '0.82')) # 早于该时刻且尚无当日排餐 → 不做当前餐段限制,直接走大库 DISH_MEAL_CUTOFF_HOUR = int(os.environ.get('DISH_MEAL_CUTOFF_HOUR', '7')) # 主食小库:不在每日排餐中,常驻候选;与「当前餐段菜品小库」双路匹配 # 环境变量 DISH_STAPLE_NAMES 逗号分隔覆盖;仅当本地模板库已有同名模板时才参与比对 _DISH_STAPLE_NAMES_DEFAULT = ( '米饭,白米饭,杂粮饭,黑米饭,馒头,花卷,包子,面条,汤面,炒饭,盖浇饭,粥,白粥,小米粥,馄饨,饺子,' '酸奶,香蕉,苹果,梨子,葡萄,营养汤,土豆,黄瓜,山药,红薯,玉米' ) DISH_STAPLE_NAMES = [ x.strip() for x in os.environ.get('DISH_STAPLE_NAMES', _DISH_STAPLE_NAMES_DEFAULT).split(',') if x.strip() ] DISH_STAPLE_LIB_THRESHOLD = float(os.environ.get('DISH_STAPLE_LIB_THRESHOLD', '0.80')) DISH_STAPLE_LIB_SOFT_THRESHOLD = float(os.environ.get('DISH_STAPLE_LIB_SOFT_THRESHOLD', '0.81')) # 主食与餐段菜都达标时:主食分 ≥ 菜分 - margin 则优先认主食(减轻主食被菜品抢走) DISH_STAPLE_PRIORITY_MARGIN = float(os.environ.get('DISH_STAPLE_PRIORITY_MARGIN', '0.03')) # 每餐开始前多少分钟拉取排餐(默认 60 = 开餐前 1 小时) DISH_MEAL_PREFETCH_MINUTES = int(os.environ.get('DISH_MEAL_PREFETCH_MINUTES', '60')) # 尚无餐段信息时的兜底拉取时刻(启动后第一次排餐未到位时用) DISH_MEAL_REFRESH_HOUR = int(os.environ.get('DISH_MEAL_REFRESH_HOUR', '6')) DISH_MEAL_REFRESH_MINUTE = int(os.environ.get('DISH_MEAL_REFRESH_MINUTE', '0')) # 实时扫描:未知菜写入待学习前的最短稳定秒数(客人放盘短,默认 1 秒) # 盘子移走时不刷入,避免移盘过程中的误检未知 DISH_LIVE_PENDING_STABLE_SECONDS = float(os.environ.get('DISH_LIVE_PENDING_STABLE_SECONDS', '1.0')) # 运动判定:从动到静后,连续多少帧与静止锚点相似,才触发 YOLO 一次(按相机帧,不按秒)。 # 持续静止不再识别,须再经历「静→动→静」。默认 4 帧(约对应原 0.2s@20Hz 轮询)。 DISH_LIVE_STILL_HOLD_FRAMES = int(os.environ.get('DISH_LIVE_STILL_HOLD_FRAMES', '4')) # 已有订单时,菜品区域相似度阈值(更低=更宽松,手在中间晃仍算静止,减少误触发) DISH_LIVE_MOTION_SIM_WITH_ORDER = float(os.environ.get('DISH_LIVE_MOTION_SIM_WITH_ORDER', '0.970')) # 订单防抖:菜品名称集合去重 + 时间间隔(秒),避免同一次晃动重复上传 # 默认 2s(建议 2~3s) DISH_LIVE_ORDER_DEBOUNCE_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_DEBOUNCE_SECONDS', '2')) # 无餐盘持续该秒数后清场解锁,允许下一盘(含相同菜名)重新存图/上报 DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS = float(os.environ.get('DISH_LIVE_ORDER_CLEAR_HOLD_SECONDS', '1.5')) # 同场景去重:按预览帧计数(约 25fps,默认 40 帧 ≈ 1.6s)。 # 窗口内即使个别菜名不同,只要检测框布局相近也视为同一单(不重复上报) DISH_LIVE_ORDER_SAME_SCENE_FRAMES = int(os.environ.get('DISH_LIVE_ORDER_SAME_SCENE_FRAMES', '40')) # 框匹配 IoU 阈值;匹配对数占比阈值(0~1) DISH_LIVE_ORDER_LAYOUT_IOU = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_IOU', '0.40')) DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO = float(os.environ.get('DISH_LIVE_ORDER_LAYOUT_MATCH_RATIO', '0.70')) # YOLO 检测需直接丢弃的类别(不裁剪、不识别、不上报),逗号分隔 DISH_DETECT_FILTER_NAMES = [ x.strip() for x in os.environ.get('DISH_DETECT_FILTER_NAMES', '手,空盘').split(',') if x.strip() ] # 贴边不完整框过滤:托盘刚入画只露一角时 YOLO 常会检出,直接丢弃 # 框任一边缘距画面边界 ≤ margin 像素,视为贴边 DISH_DETECT_EDGE_MARGIN_PX = int(os.environ.get('DISH_DETECT_EDGE_MARGIN_PX', '8')) # 贴边且框面积占整图比例低于此值 → 丢弃(默认 2%) DISH_DETECT_EDGE_MIN_AREA_RATIO = float(os.environ.get('DISH_DETECT_EDGE_MIN_AREA_RATIO', '0.02')) # 贴边且框较短边像素低于此值 → 丢弃 DISH_DETECT_EDGE_MIN_SIDE_PX = int(os.environ.get('DISH_DETECT_EDGE_MIN_SIDE_PX', '48')) # 任意框(不论是否贴边)面积占比低于此值也丢弃;0=关闭 DISH_DETECT_MIN_AREA_RATIO = float(os.environ.get('DISH_DETECT_MIN_AREA_RATIO', '0.008')) # 用本地视频文件夹代替相机实时流(调试/回放,主要给海康入口用)。 # 1=读 DISH_VIDEO_SOURCE_DIR,0=读相机。usb_camera_app 固定读 USB 相机,不受此项影响。 DISH_USE_VIDEO_FOLDER = os.environ.get('DISH_USE_VIDEO_FOLDER', '0') == '1' DISH_VIDEO_SOURCE_DIR = os.environ.get('DISH_VIDEO_SOURCE_DIR', 'videos') # 相机预览流本地录像:相机连接后自动录,只保留最近 N 分钟(滚动删除旧段) # 视频回放模式下默认关闭,避免边读边写同一目录 _STREAM_RECORD_DEFAULT = '0' if DISH_USE_VIDEO_FOLDER else '1' DISH_STREAM_RECORD_ENABLED = os.environ.get( 'DISH_STREAM_RECORD_ENABLED', _STREAM_RECORD_DEFAULT, ) == '1' DISH_STREAM_RECORD_DIR = os.environ.get('DISH_STREAM_RECORD_DIR', 'videos') DISH_STREAM_RETENTION_MINUTES = float(os.environ.get('DISH_STREAM_RETENTION_MINUTES', '3000')) # 单段时长(秒),默认 5 分钟一切,便于清理 DISH_STREAM_SEGMENT_SECONDS = float(os.environ.get('DISH_STREAM_SEGMENT_SECONDS', '350')) DISH_STREAM_RECORD_FPS = float(os.environ.get('DISH_STREAM_RECORD_FPS', '10')) DISH_STREAM_MAX_WIDTH = int(os.environ.get('DISH_STREAM_MAX_WIDTH', '1280')) DEBUG = True LOG_DIR = os.path.join(os.getcwd(), f'log/{time.strftime("%Y-%m-%d")}.log') LOG_FORMAT = '{level: <8} {time:YYYY-MM-DD HH:mm:ss.SSS} - {name}:{function} - {message}'