#!/usr/bin/env python3 """ 数据导入流水线配置 统一管理数据源路径、数据库连接参数、导入脚本路径 """ import os import glob import sys # ============================================================ # 路径常量 # ============================================================ PIPELINE_DIR = os.path.dirname(os.path.abspath(__file__)) DB_DIR = os.path.dirname(PIPELINE_DIR) BASE_DIR = os.path.dirname(DB_DIR) # 数据源根目录(西部马华数据分析 下有完整的业务数据) DATA_ROOT = os.path.join(os.path.dirname(BASE_DIR), '西部马华数据分析') # SBrainCO3 数据目录(部分文件只在这里有) DATA_SBRAIN = os.path.join(BASE_DIR, '数据') # ============================================================ # 数据库配置 # ============================================================ DB_CONFIG = { 'host': 'localhost', 'port': 5432, 'user': 'freedak', 'password': '', } # ============================================================ # 月份配置 # ============================================================ MONTH_CONFIG = { '2026-04-01': { 'month_dir': '4月', 'date_range': '2026-04-01--2026-04-30', 'dist_half1': '2026年4月1-15日', 'dist_half2': '2026年4月16-30日', 'inventory_file': '4.13盘点倒挤成本报表2026年4月明细.xlsx', 'dish_cost_file': '菜品成本分析报表.xlsx', 'expense_file': '2026年4月营业费用分析.xls', 'ck_subdir': '中央厨房4月', 'salary_file': '薪资拆分明细表_脱敏.xlsx', 'attendance_file': '考勤数据表-脱敏.xlsx', 'store_info_file': '各店信息新(20260508).xls', 'bill_records_subdir': '账单查询', 'dish_sales_subdir': '账单查询-菜品销售明细表', 'data_subdirs': {}, # 4月文件平铺在月份目录下 }, '2026-05-01': { 'month_dir': '5月', 'date_range': '2026-05-01--2026-05-31', 'dist_half1': '2026年5月1-15日', 'dist_half2': '2026年5月16-31日', 'inventory_file': '4.13盘点倒挤成本报表2026年5月.xlsx', 'dish_cost_file': '2026年5月菜品成本分析报表.xlsx', 'expense_file': '2026年5月营业费用分析.xls', 'ck_subdir': '中央厨房', 'salary_file': '2026年5月薪资拆分明细表_北京西部马华餐饮有限公司.xlsx', 'attendance_file': '2026年5月考勤数据表.xlsx', 'store_info_file': None, # 5月无新各店信息文件,复用4月 'bill_records_subdir': None, # 5月无独立账单查询目录 'dish_sales_subdir': '账单查询-菜品销售明细表', 'data_subdirs': { # 5月文件在子目录中 'inventory': '供应链数据', 'dish_cost': '营业数据', 'operating_expense': '', 'distribution': '供应链数据', 'salary': '营业数据', 'attendance': '营业数据', 'central_kitchen': '中央厨房', 'dish_sales': '营业数据', }, }, } def _find_file(base_dirs, pattern): """在多个候选目录中查找文件""" for d in base_dirs: if not d: continue path = os.path.join(d, pattern) if os.path.isfile(path): return path matches = glob.glob(os.path.join(d, pattern)) if matches: return matches[0] return None def get_data_sources(month): """ 根据月份返回数据源路径字典 month: 'YYYY-MM-01' 格式 """ cfg = MONTH_CONFIG.get(month) if not cfg: print(f"警告: 未知月份 {month},请在 config.py MONTH_CONFIG 中添加配置") return {} month_dir = cfg['month_dir'] data_dir = os.path.join(DATA_ROOT, month_dir) sbrain_data = os.path.join(DATA_SBRAIN, month_dir) subdirs = cfg.get('data_subdirs', {}) sources = {} def resolve(subdir_key, filename): """在 data_dir 和 sbrain_data 中按子目录结构查找文件""" sub = subdirs.get(subdir_key, '') candidates = [] if sub: candidates.append(os.path.join(sbrain_data, sub, filename)) candidates.append(os.path.join(data_dir, sub, filename)) candidates.append(os.path.join(sbrain_data, filename)) candidates.append(os.path.join(data_dir, filename)) for c in candidates: if os.path.isfile(c): return c return candidates[0] def resolve_dir(subdir_key, dirname): """在 data_dir 和 sbrain_data 中按子目录结构查找目录""" sub = subdirs.get(subdir_key, '') candidates = [] if sub: candidates.append(os.path.join(sbrain_data, sub, dirname)) candidates.append(os.path.join(data_dir, sub, dirname)) candidates.append(os.path.join(sbrain_data, dirname)) candidates.append(os.path.join(data_dir, dirname)) for c in candidates: if os.path.isdir(c): return c return candidates[0] # 1. 账单查询(bill_records)- 目录 bill_subdir = cfg.get('bill_records_subdir') if bill_subdir: sources['bill_records'] = resolve_dir('bill_records', bill_subdir) else: sources['bill_records'] = None # 2. 菜品销售明细 - 目录 sources['dish_sales'] = resolve_dir('dish_sales', cfg['dish_sales_subdir']) # 3. 库存成本 sources['inventory'] = resolve('inventory', cfg['inventory_file']) # 4. 菜品成本BOM sources['dish_cost'] = resolve('dish_cost', cfg['dish_cost_file']) # 5. 营业费用 sources['operating_expense'] = resolve('operating_expense', cfg['expense_file']) # 6. 中央厨房(4个子文件) ck_subdir = cfg['ck_subdir'] ck_dir_sbrain = os.path.join(sbrain_data, ck_subdir) ck_dir_data = os.path.join(data_dir, ck_subdir) ck_dir = ck_dir_sbrain if os.path.isdir(ck_dir_sbrain) else ck_dir_data date_range = cfg['date_range'] sources['central_kitchen_finished'] = _find_file( [ck_dir], f'{date_range} 23 59 59完工入库统计分析报表.xlsx' ) or os.path.join(ck_dir, f'{date_range} 23 59 59完工入库统计分析报表.xlsx') sources['central_kitchen_recipe'] = _find_file( [ck_dir], f'按配方导出-货品实际与理论耗用({date_range}).xlsx' ) or os.path.join(ck_dir, f'按配方导出-货品实际与理论耗用({date_range}).xlsx') sources['central_kitchen_material'] = _find_file( [ck_dir], f'按货品导出-货品实际与理论耗用({date_range}).xlsx' ) or os.path.join(ck_dir, f'按货品导出-货品实际与理论耗用({date_range}).xlsx') sources['central_kitchen_processing'] = _find_file( [ck_dir], '加工单价分析报表.xlsx' ) or os.path.join(ck_dir, '加工单价分析报表.xlsx') # 7. 配送明细(2个文件) sources['distribution'] = resolve('distribution', f'全部门店-全部仓库-统计货品明细报表{cfg["dist_half1"]}.xlsx') sources['distribution_2'] = resolve('distribution', f'全部门店-全部仓库-统计货品明细报表{cfg["dist_half2"]}.xlsx') # 8. 薪资+考勤 sources['salary'] = resolve('salary', cfg['salary_file']) sources['attendance'] = resolve('attendance', cfg['attendance_file']) # 9. 门店位置 store_info_file = cfg.get('store_info_file') if store_info_file: sources['store_location'] = resolve('', store_info_file) else: # 5月无新文件,复用4月 for p in [ os.path.join(DATA_SBRAIN, '4月', '各店信息新(20260508).xls'), os.path.join(DATA_ROOT, '4月', '各店信息新(20260508).xls'), ]: if os.path.isfile(p): sources['store_location'] = p break else: sources['store_location'] = None return sources # ============================================================ # 物化视图刷新顺序(按依赖分层,同层可并行) # ============================================================ MATVIEW_LAYERS = [ # L0: 基础物化视图,依赖基础表 ['bill_fact'], # L1: 依赖 bill_fact 或基础表 [ 'mv_daily_revenue', 'mv_dish_basket_monthly', 'mv_dish_pair_summary_monthly', 'mv_dish_sku_summary_monthly', 'mv_dish_store_summary_monthly', 'mv_district_site_benchmark_monthly', 'mv_inventory_cost_classified_monthly', 'mv_site_segment_benchmark_monthly', 'mv_store_action_priority_deep_monthly', 'mv_store_area_efficiency_monthly', 'mv_store_benchmark_composite_monthly', 'mv_store_category_mix_monthly', 'mv_store_deep_diagnosis_monthly', 'mv_store_member_opportunity_monthly', 'mv_store_overlap_risk_monthly', 'mv_store_platform_economics_monthly', 'mv_store_repeat_summary_monthly', 'mv_store_risk_rating_monthly', 'mv_store_scorecard', 'mv_store_site_profile_monthly', 'mv_store_site_replication_monthly', 'mv_store_theoretical_actual_cost_monthly', 'dish_sales_april', 'v_store_benchmark', 'v_store_category_mix', 'v_store_platform_economics', ], # L2: 依赖 L1 物化视图 [ 'dish_basket_april', 'dish_category_summary_april', 'dish_member_sku_april', 'dish_sku_summary_april', 'dish_store_sku_april', 'mv_dish_sku_abc_monthly', 'v_store_action_list', ], # L3: 依赖 L2 [ 'dish_store_summary_april', 'mv_store_action_priority_deep_april', 'mv_store_theoretical_actual_cost_april', 'v_store_execution_priority', ], ] def get_dbname(): """获取默认数据库名""" return 'bill_query_test'