Skill: jinqianfei/warehouse-network-optimizer (version 2.7.0)

--- name: warehouse-network-optimizer description: "基于整数规划（MILP）的仓配网络优化系统，支持时效计算（含等待时间）、中转链路、供应关系到区县等。" allowed-tools: Bash(python3:*), read, write --- # 仓配网络规划优化器 Skill v2.7 ## 概述 基于整数规划（MILP）的仓配网络优化系统，使用 PuLP+CBC 求解器，支持多约束优化。 ## 版本历史 | 版本 | 日期 | 更新内容 | |------|------|----------| | **v2.7** | 2026-04-07 | **RDC数量改为上限（<=N）**，新增参数引导流程 | | v2.6 | 2026-04-07 | 智能单位检测 + 加权平均时效指标 | | v2.5 | 2026-04-07 | 优化门店匹配逻辑（城市优先，省份备选） | | v2.4 | 2026-04-07 | 新增等待时间计算（考虑发车日） | --- ## 📋 完整使用流程 ``` Step 1: 准备数据文件 ↓ Step 2: 转换数据（Excel → JSON） ↓ Step 3: 配置优化参数 ⭐【新增引导流程】 ↓ Step 4: 运行优化 → 生成 Excel 报告 ``` --- ## 🎯 Step 3: 参数配置（重要更新 v2.7） ### 参数引导清单 **1. 基本参数** | 参数 | 说明 | 格式 | |------|------|------| | RDC数量上限 | 最多选择的RDC数量 | 数字（如5） | **2. 约束条件（可选）** | 参数 | 说明 | 格式 | |------|------|------| | 必选仓库 | 必须包含的RDC仓库 | 仓库名列表 | | 禁止仓库 | 不能作为RDC的仓库 | 仓库名列表 | **⚠️ 重要：RDC数量含义** - 旧逻辑：`== N`（恰好N个） - **新逻辑：`<= N`（最多N个，由优化器自动选择最优数量）** --- ## 🔴 v2.7 重要变更 ### 1. RDC数量改为上限 **变更前：** ``` 设置 RDC=5 → 必须恰好选择5个RDC ``` **变更后：** ``` 设置 RDC<=5 → 最多选择5个RDC，优化器自动选择最优数量 优点：更灵活，避免过度建设 ``` ### 2. 参数引导流程 **优化前必须确认的参数：** ``` 1. RDC数量上限（N） 2. 必选仓库（如有） 3. 禁止仓库（如有） ``` --- ## 🚀 快速开始 ### 1. 安装依赖 ```bash pip install pulp pandas openpyxl ``` ### 2. 准备数据文件 | 数据类型 | 格式 | 说明 | |----------|------|------| | 门店数据 | Excel | 省份、城市、区县、详细地址 | | 干线数据 | Excel | 仓库间调拨线路和时效 | | 共配数据 | Excel（多Sheet） | 各基地报货周期表 | ### 3. 转换数据 ```bash # 转换干线数据（自动检测单位并转换） python3 convert_data.py --trunk 干线数据.xlsx --output-dir . # 转换共配数据 python3 convert_data.py --distribution 共配报货周期表.xlsx --output-dir . ``` ### 4. 配置参数并运行 ```bash python3 run_optimization.py \ --store-file 门店数据.xlsx \ --rdc-count 5 \ # 最多5个RDC --output result.xlsx ``` --- ## 📊 优化算法逻辑 ### 1. 目标函数 ``` 最小化：Σ(门店数 × 线路时效) 加权和 ``` ### 2. 约束条件 ``` 1. 每个省份必须分配给1个RDC 2. RDC数量 <= 指定上限 3. 必选仓库（如果有）必须被选中 4. 禁止仓库（如果有）不能被选中 ``` ### 3. 时效计算 **总时效 = 等待时间 + 干线时效 + 等待时间 + 配送时效** | 时效类型 | 单位 | 说明 | |----------|------|------| | 干线时效 | **天** | 由小时转换或直接使用 | | 配送时效 | **天** | 共配表原始数据 | | 等待时间 | **天** | 因发车日导致的等待 | ### 4. 链路类型判断 | 链路类型 | 判断条件 | |----------|----------| | **直达** | RDC = 配送基地 | | **调拨一次** | RDC ≠ FSL，有直达干线 | | **中转** | 需要经过TC中转仓 | ### 5. 门店匹配逻辑 ``` 1. 优先城市精确匹配 2. 其次省份模糊匹配 3. 城市推断省份 ``` --- ## 📊 输出Excel结构 | Sheet名称 | 内容 | |-----------|------| | **执行摘要** | 关键指标、RDC数量、覆盖区域、加权平均时效 | | **RDC选址** | 选中的RDC仓库 | | **供应关系** | 完整链路信息（含时效分解） | | **调拨中转仓** | 涉及的中转链路 | | **省份分布** | 各省归属 | ### 执行摘要字段 | 字段 | 说明 | |------|------| | RDC数量 | 实际选中的RDC数量（≤上限） | | 覆盖区域 | 省-市-区组合数 | | 总门店数 | 有效门店总数 | | 时效单位 | 天 | | 加权平均时效 | Σ(门店数×时效)/总门店数 | --- ## 📝 代码示例 ### 方式1：命令行 ```bash python3 run_optimization.py \ --store-file 门店数据.xlsx \ --rdc-count 5 \ --output result.xlsx ``` ### 方式2：Python调用 ```python from run_optimization import optimize_and_generate_excel optimize_and_generate_excel( store_file='门店数据.xlsx', num_rdc=5, # 最多5个RDC output_file='result.xlsx' ) ``` --- ## ✅ 检查清单 - [x] **RDC数量改为上限（<= N）** - [x] 参数引导流程 - [x] 智能单位检测 - [x] 加权平均时效指标 - [x] 时效计算（含等待时间） - [x] 链路类型判断 - [x] 供应关系展示到区县级别 - [x] 门店匹配逻辑（城市优先，省份备选） --- ## 📁 文件结构 ``` warehouse-network-optimizer.skills/ ├── SKILL.md # 本文档 ├── convert_data.py # 数据转换脚本 ├── run_optimization.py # 核心优化脚本 ├── trunk_lines.json # 干线数据 └── distribution_cycles.json # 共配数据 ``` --- **Status**: 生产可用 ✅ **Version**: 2.7.0 **核心功能**: 整数规划优化 + 智能单位检测 + 加权平均时效

path: convert_data.py
content: #!/usr/bin/env python3
"""
数据转换脚本 - 将Excel数据转换为JSON格式
用法: python3 convert_data.py --trunk <干线Excel> --distribution <共配Excel>

依赖: pip install pandas openpyxl

⚠️ 智能单位检测：
  1. 优先从表头/备注读取单位标识
  2. 根据数据特征（速度计算）自动推断
  3. 无法确定时提示用户
"""

import pandas as pd
import json
import re
import argparse
import os


def detect_time_unit(df, efficiency_col=12, distance_col=6):
    """
    智能检测时效单位（小时/天）
    
    检测策略：
    1. 搜索表头、备注中是否包含"小时"、"天"、"h"、"d"等标识
    2. 根据速度计算推断（合理速度约30-80km/h）
    """
    # 策略1: 搜索表头中的单位标识
    for row_idx in range(min(5, len(df))):
        for col_idx in range(len(df.columns)):
            val = str(df.iloc[row_idx, col_idx]).lower()
            if '小时' in val or 'h' in val or 'hr' in val:
                print(f'  [检测] 表头发现小时标识: {val}')
                return 'hours'
            if ('天' in val and '每' not in val) or val in ['d', 'day']:
                print(f'  [检测] 表头发现天标识: {val}')
                return 'days'
    
    # 策略2: 搜索"在途时效"附近列是否有单位
    for col_idx in range(max(0, efficiency_col-2), min(len(df.columns), efficiency_col+3)):
        for row_idx in range(min(5, len(df))):
            val = str(df.iloc[row_idx, col_idx]).lower()
            if '小时' in val or 'h' in val:
                print(f'  [检测] 时效列附近发现小时标识: {val}')
                return 'hours'
    
    # 策略3: 根据速度计算推断
    print('  [检测] 尝试根据速度计算推断单位...')
    speeds_hours = []
    speeds_days = []
    
    for idx in range(3, min(20, len(df))):
        distance = df.iloc[idx, distance_col]
        efficiency = df.iloc[idx, efficiency_col]
        if pd.isna(distance) or pd.isna(efficiency):
            continue
        try:
            dist = float(distance)
            eff = float(efficiency)
            if eff > 0 and dist > 0:
                speed_per_hour = dist / eff  # 如果efficiency是小时
                speed_per_day = dist / eff    # 如果efficiency是天
                speeds_hours.append(speed_per_hour)
                speeds_days.append(speed_per_day)
        except:
            continue
    
    if speeds_hours and speeds_days:
        avg_speed_hour = sum(speeds_hours) / len(speeds_hours)
        avg_speed_day = sum(speeds_days) / len(speeds_days)
        
        print(f'  [检测] 如果是小时: 平均速度 = {avg_speed_hour:.1f} km/h')
        print(f'  [检测] 如果是天:   平均速度 = {avg_speed_day:.1f} km/天')
        
        # 判断哪个更合理
        # 干线运输合理速度: 30-80 km/h（考虑堵车、装卸等）
        # 如果是天: 应该 > 500 km/天 才合理
        if 20 < avg_speed_hour < 100:
            print(f'  [检测] 推断结果: 小时 ✅ (速度 {avg_speed_hour:.1f} km/h 合理)')
            return 'hours'
        elif avg_speed_day > 300:
            print(f'  [检测] 推断结果: 天 ✅ (速度 {avg_speed_day:.1f} km/天 合理)')
            return 'days'
        else:
            print(f'  [检测] 两种都有可能，请人工确认')
            # 默认假设为小时（从业务角度更常见）
            return 'hours'
    
    # 默认返回小时
    print('  [检测] 无法确定，默认假设为小时')
    return 'hours'


def convert_trunk_lines(excel_path):
    """转换干线数据"""
    print(f'转换干线数据: {excel_path}')
    df = pd.read_excel(excel_path, header=None)
    
    warehouse_map = {
        '合肥': 1, '南京': 2, '郑州': 3, '杭州': 4, '济南': 5,
        '上海': 6, '西安': 7, '天津': 8, '沈阳': 9, '武汉': 10,
        '长沙': 11, '北京': 12, '成都': 13, '石家庄': 14, '乌鲁木齐': 15,
        '太原': 16, '哈尔滨': 17, '泉州': 18, '南昌': 19, '兰州': 20,
        '广州': 21, '鹿邑': 22, '长春': 23
    }
    
    # ⭐ 智能检测单位
    print('检测时效单位...')
    time_unit = detect_time_unit(df)
    
    def convert_shipping_days(s):
        if pd.isna(s): return '1,2,3,4,5,6,7'
        s = str(s).replace(' ', '')
        days = []
        if '周一' in s or '一' in s: days.append(1)
        if '周二' in s or '二' in s: days.append(2)
        if '周三' in s or '三' in s: days.append(3)
        if '周四' in s or '四' in s: days.append(4)
        if '周五' in s or '五' in s: days.append(5)
        if '周六' in s or '六' in s: days.append(6)
        if '周日' in s or '七' in s: days.append(7)
        return ','.join(map(str, sorted(set(days)))) if days else '1,2,3,4,5,6,7'
    
    def convert_efficiency(s):
        """转换时效值"""
        if pd.isna(s): return 1.0
        try:
            value = float(s)
            if time_unit == 'hours':
                return round(value / 24, 3)  # 小时转天
            else:
                return value  # 已经是天
        except:
            return 1.0
    
    records = []
    for idx, row in df.iterrows():
        if idx < 3: continue
        from_wh = str(row[3]).strip() if pd.notna(row[3]) else None
        to_wh = str(row[4]).strip() if pd.notna(row[4]) else None
        if not from_wh or not to_wh: continue
        
        from_id = warehouse_map.get(from_wh, 99)
        to_id = warehouse_map.get(to_wh, 99)
        hours = float(row[12]) if pd.notna(row[12]) else 24
        
        records.append({
            'id': len(records) + 1,
            'from_house_id': from_id,
            'from_house_name': f'{from_wh}仓',
            'to_house_id': to_id,
            'to_house_name': f'{to_wh}仓',
            'line_name': f'{from_wh}-{to_wh}',
            'distance': int(row[6]) if pd.notna(row[6]) else 0,
            'efficiency_original': hours,  # 原始值
            'efficiency': convert_efficiency(row[12]),  # 转换后
            'time_unit': time_unit,  # ⭐ 记录检测到的单位
            'shipping_days': convert_shipping_days(row[7]),
            'transport_mode': str(row[1]) if pd.notna(row[1]) else '单程',
            'line_type': '调拨'
        })
    
    return {'trunk_lines': records, 'time_unit': time_unit}


def convert_distribution_cycles(excel_path):
    """转换共配数据"""
    print(f'转换共配数据: {excel_path}')
    xlsx = pd.ExcelFile(excel_path)
    
    warehouse_map = {
        '长沙': '长沙仓', '济南': '济南仓', '沈阳': '沈阳仓', '武汉': '武汉仓',
        '西安': '西安仓', '南昌': '南昌仓', '天津': '天津仓', '太原': '太原仓',
        '广州': '广州仓', '北京': '北京仓', '合肥': '合肥仓', '郑州': '郑州仓',
        '石家庄': '石家庄仓', '哈尔滨': '哈尔滨仓', '南京': '南京仓', '杭州': '杭州仓',
        '上海': '上海仓', '泉州': '泉州仓', '兰州': '兰州仓', '成都': '成都仓'
    }
    
    weekday_map = {'周一': 1, '周二': 2, '周三': 3, '周四': 4, '周五': 5, '周六': 6, '周日': 7}
    
    def convert_efficiency(s):
        if pd.isna(s): return 1
        nums = re.findall(r'\d+', str(s))
        return int(nums[0]) if nums else 1
    
    all_records = []
    
    for sheet in xlsx.sheet_names:
        df = pd.read_excel(xlsx, sheet_name=sheet, header=None)
        warehouse_name = warehouse_map.get(sheet, f'{sheet}仓')
        
        header_row, monday_col = None, None
        for idx in range(min(5, len(df))):
            for col in range(len(df.columns)):
                if str(df.iloc[idx, col]).strip() == '周一':
                    header_row, monday_col = idx, col
                    break
            if monday_col: break
        
        if monday_col is None: continue
        
        eff_col = monday_col - 2
        current_province, current_city = None, None
        
        for idx in range(header_row + 1, len(df)):
            row = df.iloc[idx]
            if pd.notna(row[0]) and str(row[0]).strip():
                current_province = str(row[0]).strip()
            if pd.notna(row[1]) and str(row[1]).strip():
                current_city = str(row[1]).strip()
            if current_province is None or current_city is None: continue
            
            area = str(row[2]).strip() if pd.notna(row[2]) else '全区域'
            efficiency = convert_efficiency(row[eff_col]) if eff_col < len(row) else 1
            
            shipping_dates = []
            for offset, name in enumerate(['周一', '周二', '周三', '周四', '周五', '周六', '周日']):
                col = monday_col + offset
                if col < len(row) and pd.notna(row[col]):
                    val = str(row[col]).strip()
                    if val and val not in ['NaN'] and ('周' in val or val in ['√', 'V', 'v', 'Y', 'y']):
                        shipping_dates.append(weekday_map[name])
            
            if shipping_dates:
                all_records.append({
                    'start_house_name': warehouse_name,
                    'province_name': current_province,
                    'city_name': current_city,
                    'area_name': area,
                    'efficiency': efficiency,
                    'shipping_date': ','.join(map(str, sorted(set(shipping_dates))))
                })
    
    return {'distribution_cycles': all_records}


def main():
    parser = argparse.ArgumentParser(description='数据转换工具')
    parser.add_argument('--trunk', help='干线数据Excel文件')
    parser.add_argument('--distribution', help='共配数据Excel文件（多Sheet）')
    parser.add_argument('--output-dir', default='.', help='输出目录')
    args = parser.parse_args()
    
    if args.trunk:
        data = convert_trunk_lines(args.trunk)
        time_unit = data.pop('time_unit')  # 单独记录单位
        output_path = os.path.join(args.output_dir, 'trunk_lines.json')
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print(f'✅ 干线数据已保存: {output_path} ({len(data["trunk_lines"])} 条)')
        print(f'⚠️ 检测到的时效单位: {time_unit}')
        if time_unit == 'hours':
            print('   已自动转换为天（÷24）')
    
    if args.distribution:
        data = convert_distribution_cycles(args.distribution)
        output_path = os.path.join(args.output_dir, 'distribution_cycles.json')
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print(f'✅ 共配数据已保存: {output_path} ({len(data["distribution_cycles"])} 条)')
    
    if not args.trunk and not args.distribution:
        print('请指定 --trunk 或 --distribution 参数')


if __name__ == '__main__':
    main()


path: run_optimization.py
content: #!/usr/bin/env python3
"""
仓配网络规划优化 - 核心脚本 v2.6

更新日志:
- v2.6: 智能单位检测，自动转换时效单位
- v2.5: 优化门店匹配逻辑（城市优先，省份备选）
- v2.4: 新增等待时间计算

用法: python3 run_optimization.py --store-file <门店Excel> [--rdc-count <数量>] [--output <输出文件>]
"""

import json
import pandas as pd
import pulp
from collections import defaultdict, deque
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
from openpyxl.utils import get_column_letter
import argparse
import os


def load_trunk_data(file_path='trunk_lines.json'):
    """加载干线数据"""
    with open(file_path, 'r', encoding='utf-8') as f:
        trunk_data = json.load(f)
    trunk_lines = trunk_data['trunk_lines']
    
    trunk_graph = defaultdict(list)
    trunk_shipping_days = {}
    for line in trunk_lines:
        trunk_graph[line['from_house_id']].append({
            'to': line['to_house_id'],
            'efficiency': line['efficiency']
        })
        trunk_shipping_days[(line['from_house_id'], line['to_house_id'])] = \
            [int(x) for x in line['shipping_days'].split(',')]
    
    warehouse_name_map = {}
    for line in trunk_lines:
        warehouse_name_map[line['from_house_id']] = line['from_house_name']
        warehouse_name_map[line['to_house_id']] = line['to_house_name']
    
    warehouse_list = [{'id': k, 'name': v} for k, v in warehouse_name_map.items()]
    
    return trunk_graph, warehouse_list, warehouse_name_map, trunk_shipping_days


def load_distribution_data(file_path='distribution_cycles.json'):
    """加载共配数据"""
    with open(file_path, 'r', encoding='utf-8') as f:
        dist_data = json.load(f)
    
    dist_efficiency = defaultdict(dict)
    dist_shipping_days = defaultdict(dict)
    
    for d in dist_data['distribution_cycles']:
        key_city = (d['province_name'], d['city_name'])
        house = d['start_house_name']
        
        if key_city not in dist_efficiency[house]:
            dist_efficiency[house][key_city] = d['efficiency']
        else:
            dist_efficiency[house][key_city] = min(dist_efficiency[house][key_city], d['efficiency'])
        
        if key_city not in dist_shipping_days[house]:
            dist_shipping_days[house][key_city] = [int(x) for x in d['shipping_date'].split(',')]
    
    province_map = defaultdict(lambda: defaultdict(list))
    for house, cities in dist_efficiency.items():
        for (prov, city), eff in cities.items():
            province_map[house][prov].append(city)
    
    return dist_efficiency, dist_shipping_days, province_map


def find_best_distribution(province, city, dist_efficiency, province_map):
    """查找最佳配送基地"""
    for house, cities in dist_efficiency.items():
        for (prov, c), eff in cities.items():
            if province in prov and city in c:
                return house, (prov, c), eff
    
    for house, provinces in province_map.items():
        for prov, cities in provinces.items():
            if province in prov:
                return house, (prov, cities[0]), 2
    
    return None, None, None


def get_next_shipping_day(arrival_day, shipping_days):
    """计算等待天数"""
    if arrival_day in shipping_days:
        return 0
    for i in range(1, 8):
        next_day = ((arrival_day + i - 1) % 7) + 1
        if next_day in shipping_days:
            return i
    return 7


def calculate_full_efficiency(rdc_id, fsl_name, province, city, trunk_graph, 
                            warehouse_list, dist_efficiency, dist_shipping_days, trunk_shipping_days):
    """计算完整时效（含等待时间）"""
    fsl_id = None
    for w in warehouse_list:
        if w['name'] == fsl_name:
            fsl_id = w['id']
            break
    if not fsl_id:
        return None

    if rdc_id == fsl_id:
        dist_eff = dist_efficiency.get(fsl_name, {}).get((province, city), 2)
        dist_days = dist_shipping_days.get(fsl_name, {}).get((province, city), [1,2,3,4,5,6,7])
        wait = get_next_shipping_day(1, dist_days)
        return {'total_days': wait + dist_eff, 'wait_days': wait, 'trunk_days': 0, 'dist_days': dist_eff}

    queue = deque([(rdc_id, [rdc_id], 0, 0, 0)])
    visited = {rdc_id}

    while queue:
        current, path, transit, wait_days, hops = queue.popleft()
        if hops > 2:
            continue
        for next_hop in trunk_graph.get(current, []):
            next_id = next_hop['to']
            line_eff = next_hop['efficiency']
            arrival_day = ((1 + int(transit)) % 7) + 1
            key = (current, next_id)
            shipping = trunk_shipping_days.get(key, [1,2,3,4,5,6,7])
            wait = get_next_shipping_day(arrival_day, shipping)
            new_transit = transit + wait + line_eff
            new_wait = wait_days + wait
            new_path = path + [next_id]

            if next_id == fsl_id:
                dist_eff = dist_efficiency.get(fsl_name, {}).get((province, city), 2)
                dist_days = dist_shipping_days.get(fsl_name, {}).get((province, city), [1,2,3,4,5,6,7])
                final_arrival = ((1 + int(new_transit)) % 7) + 1
                dist_wait = get_next_shipping_day(final_arrival, dist_days)
                return {'total_days': new_transit + dist_wait + dist_eff, 
                       'wait_days': new_wait + dist_wait,
                       'trunk_days': transit + line_eff, 'dist_days': dist_eff, 'path': new_path}

            if next_id not in visited and hops < 2:
                visited.add(next_id)
                queue.append((next_id, new_path, new_transit, new_wait, hops + 1))

    return None


def process_store_data(df):
    """处理门店数据"""
    city_to_province = {
        '贵阳市': '贵州省', '成都市': '四川省', '武汉市': '湖北省',
        '南昌市': '江西省', '郑州市': '河南省', '西安市': '陕西省',
        '重庆市': '重庆市', '昆明市': '云南省', '长沙市': '湖南省',
        '漯河市': '河南省', '开封市': '河南省', '景德镇市': '江西省',
        '荆州市': '湖北省', '襄阳市': '湖北省', '新乡市': '河南省',
        '宜昌市': '湖北省', '宜宾市': '四川省', '南阳市': '河南省',
    }
    
    records = []
    for idx, row in df.iterrows():
        province = str(row['省']) if pd.notna(row.get('省')) else None
        city = str(row['市']) if pd.notna(row.get('市')) else None
        district = str(row['区']) if pd.notna(row.get('区')) else '全区域'
        
        if not city:
            continue
        
        if not province or province == 'nan':
            province = city_to_province.get(city, city + '市')
        
        records.append({
            '省份': province,
            '城市': city,
            '区县': district,
        })
    
    return pd.DataFrame(records)


def optimize_and_generate_excel(store_file, num_rdc=3, output_file='result.xlsx', 
                                trunk_file='trunk_lines.json', dist_file='distribution_cycles.json'):
    """执行优化并生成Excel"""
    print('=== 仓配网络规划优化 v2.6 ===')
    
    # 加载门店数据
    df = pd.read_excel(store_file)
    print(f'原始数据: {len(df)} 行')
    df = process_store_data(df)
    area_stats = df.groupby(['省份', '城市', '区县']).size().reset_index(name='门店数量')
    print(f'有效区域: {len(area_stats)} 个')
    
    # 加载干线数据
    trunk_graph, warehouse_list, warehouse_name_map, trunk_shipping_days = load_trunk_data(trunk_file)
    print(f'仓库: {len(warehouse_list)} 个')
    print(f'干线: {len(trunk_graph)} 条')
    
    # 加载共配数据
    dist_efficiency, dist_shipping_days, province_map = load_distribution_data(dist_file)
    print(f'共配记录: {sum(len(v) for v in dist_efficiency.values())} 条')
    
    # 优化
    print(f'\n=== 运行优化 (RDC={num_rdc}) ===')
    prob = pulp.LpProblem('WarehouseNetwork', pulp.LpMinimize)
    warehouses_ids = [w['id'] for w in warehouse_list]
    
    provinces = df['省份'].unique().tolist()
    
    y = pulp.LpVariable.dicts('y', warehouses_ids, cat='Binary')
    x = pulp.LpVariable.dicts('x', [(w['id'], p) for w in warehouse_list for p in provinces], cat='Binary')
    
    cost_terms = []
    for w in warehouse_list:
        for p in provinces:
            best_days = 5
            for fsl in warehouse_list:
                fsl_name = fsl['name']
                if fsl_name in dist_efficiency:
                    result = calculate_full_efficiency(
                        w['id'], fsl_name, p, p, trunk_graph,
                        warehouse_list, dist_efficiency, dist_shipping_days, trunk_shipping_days
                    )
                    if result:
                        best_days = min(best_days, result['total_days'])
            cost_terms.append(x[(w['id'], p)] * 1 * best_days)
    
    prob += pulp.lpSum(cost_terms)
    for p in provinces:
        prob += pulp.lpSum([x[(w['id'], p)] for w in warehouse_list]) == 1
    for w in warehouse_list:
        for p in provinces:
            prob += x[(w['id'], p)] <= y[w['id']]
    prob += pulp.lpSum([y[i] for i in warehouses_ids]) <= num_rdc  # RDC数量上限
    
    prob.solve(pulp.PULP_CBC_CMD(msg=0))
    print(f'求解状态: {pulp.LpStatus[prob.status]}')
    
    selected = [w for w in warehouse_list if y[w['id']].value() > 0.5]
    print(f'选中的RDC: {[w["name"] for w in selected]}')
    
    # 生成Excel
    print('\n=== 生成Excel ===')
    wb = Workbook()
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_font = Font(bold=True, color="FFFFFF")
    
    def style_header(ws, cols):
        for col in cols:
            ws[f'{col}1'].fill = header_fill
            ws[f'{col}1'].font = header_font
    
    # 计算加权平均时效
    supply_records = []  # 收集供应关系数据用于计算
    
    # 执行摘要
    ws1 = wb.active
    ws1.title = "执行摘要"
    ws1['A1'] = "仓配网络规划分析报告 v2.6"
    ws1['A1'].font = Font(bold=True, size=14)
    ws1['A3'] = "RDC数量"
    ws1['B3'] = len(selected)
    ws1['A4'] = "覆盖区域"
    ws1['B4'] = len(area_stats)
    ws1['A5'] = "总门店数"
    ws1['B5'] = len(df)
    ws1['A6'] = "时效单位"
    ws1['B6'] = "天"
    ws1['A7'] = "加权平均时效"
    ws1['B7'] = "待计算"  # 先占位，后面填入
    
    # RDC选址
    ws2 = wb.create_sheet("RDC选址")
    ws2['A1'] = "RDC仓库"
    ws2['B1'] = "仓库ID"
    style_header(ws2, ['A', 'B'])
    for i, w in enumerate(selected, 2):
        ws2[f'A{i}'] = w['name']
        ws2[f'B{i}'] = w['id']
    
    # 供应关系
    ws3 = wb.create_sheet("供应关系")
    headers = ["RDC仓库", "中转仓", "配送基地", "省份", "城市", "区县", 
               "门店数量", "链路类型", "等待时间", "干线时效(天)", "配送时效", "总时效(天)"]
    for i, h in enumerate(headers, 1):
        ws3.cell(row=1, column=i, value=h)
    style_header(ws3, [get_column_letter(i) for i in range(1, len(headers)+1)])
    
    transfer_records = []
    supply_records = []  # 收集供应关系用于计算加权平均
    row = 2
    for area in area_stats.itertuples():
        province, city, district = area.省份, area.城市, area.区县
        store_count = area.门店数量
        
        fsl_name, fsl_key, _ = find_best_distribution(province, city, dist_efficiency, province_map)
        
        if fsl_name:
            best_rdc, best_result = None, None
            for w in selected:
                result = calculate_full_efficiency(
                    w['id'], fsl_name, fsl_key[0], fsl_key[1], trunk_graph,
                    warehouse_list, dist_efficiency, dist_shipping_days, trunk_shipping_days
                )
                if result and (best_result is None or result['total_days'] < best_result['total_days']):
                    best_rdc = w
                    best_result = result
            
            if best_rdc and best_result:
                tc_name = ''
                if best_rdc['name'] == fsl_name:
                    link_type = '直达'
                elif len(best_result.get('path', [])) == 2:
                    link_type = '调拨一次'
                else:
                    link_type = '中转'
                    if best_result.get('path') and len(best_result['path']) > 1:
                        tc_name = warehouse_name_map.get(best_result['path'][1], '')
                        transfer_records.append({
                            'RDC': best_rdc['name'], '中转仓': tc_name, '目的仓': fsl_name,
                            '链路': ' -> '.join([warehouse_name_map.get(p, '') for p in best_result['path']]),
                            '时效': round(best_result['total_days'], 2)
                        })
                
                ws3[f'A{row}'] = best_rdc['name']
                ws3[f'B{row}'] = tc_name
                ws3[f'C{row}'] = fsl_name
                ws3[f'D{row}'] = province
                ws3[f'E{row}'] = city
                ws3[f'F{row}'] = district
                ws3[f'G{row}'] = store_count
                ws3[f'H{row}'] = link_type
                ws3[f'I{row}'] = best_result['wait_days']
                ws3[f'J{row}'] = round(best_result['trunk_days'], 2)
                ws3[f'K{row}'] = best_result['dist_days']
                ws3[f'L{row}'] = round(best_result['total_days'], 2)
                
                # 收集数据用于计算加权平均
                supply_records.append({
                    'store_count': store_count,
                    'total_days': round(best_result['total_days'], 2)
                })
                
                row += 1
    
    # 调拨中转仓
    ws4 = wb.create_sheet("调拨中转仓")
    for i, h in enumerate(["序号", "RDC仓库", "中转仓", "目的仓", "完整链路", "总时效(天)"], 1):
        ws4.cell(row=1, column=i, value=h)
    style_header(ws4, ['A', 'B', 'C', 'D', 'E', 'F'])
    
    seen, unique_transfers = set(), []
    for t in transfer_records:
        key = (t['RDC'], t['中转仓'], t['目的仓'])
        if key not in seen:
            seen.add(key)
            unique_transfers.append(t)
    for i, t in enumerate(unique_transfers, 2):
        ws4[f'A{i}'] = i - 1
        ws4[f'B{i}'] = t['RDC']
        ws4[f'C{i}'] = t['中转仓']
        ws4[f'D{i}'] = t['目的仓']
        ws4[f'E{i}'] = t['链路']
        ws4[f'F{i}'] = t['时效']
    
    # 省份分布
    ws5 = wb.create_sheet("省份分布")
    for i, h in enumerate(["省份", "区域数", "RDC归属"], 1):
        ws5.cell(row=1, column=i, value=h)
    style_header(ws5, ['A', 'B', 'C'])
    
    province_assign = {}
    for p in provinces:
        for w in selected:
            if x[(w['id'], p)].value() > 0.5:
                province_assign[p] = w['name']
                break
    for i, p in enumerate(sorted(province_assign.keys()), 2):
        ws5[f'A{i}'] = p
        ws5[f'B{i}'] = len(area_stats[area_stats['省份']==p])
        ws5[f'C{i}'] = province_assign.get(p, '')
    
    for ws in [ws3, ws4, ws5]:
        for col in range(1, ws.max_column + 1):
            ws.column_dimensions[get_column_letter(col)].width = 14
    
    # 计算加权平均时效
    total_stores = sum(r['store_count'] for r in supply_records)
    weighted_sum = sum(r['store_count'] * r['total_days'] for r in supply_records)
    weighted_avg_efficiency = weighted_sum / total_stores if total_stores > 0 else 0
    
    # 更新执行摘要
    ws1['B7'] = round(weighted_avg_efficiency, 2)
    print(f'加权平均时效: {round(weighted_avg_efficiency, 2)} 天')
    
    wb.save(output_file)
    print(f'✅ Excel报告已生成: {output_file}')
    print(f'供应关系记录: {row - 2} 条')


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='仓配网络规划优化 v2.6')
    parser.add_argument('--store-file', required=True, help='门店数据Excel文件')
    parser.add_argument('--rdc-count', type=int, default=3, help='RDC数量（默认3）')
    parser.add_argument('--output', default='result.xlsx', help='输出文件路径')
    parser.add_argument('--trunk-data', default='trunk_lines.json', help='干线数据文件')
    parser.add_argument('--dist-data', default='distribution_cycles.json', help='共配数据文件')
    args = parser.parse_args()
    
    optimize_and_generate_excel(
        args.store_file, 
        args.rdc_count, 
        args.output,
        args.trunk_data,
        args.dist_data
    )


path: trunk_lines.json
content: {
  "trunk_lines": [
    {
      "id": 1,
      "from_house_id": 1,
      "from_house_name": "合肥仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "合肥-南京",
      "distance": 170,
      "efficiency_original": 4.0,
      "efficiency": 0.167,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 2,
      "from_house_id": 1,
      "from_house_name": "合肥仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "合肥-郑州",
      "distance": 590,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 3,
      "from_house_id": 1,
      "from_house_name": "合肥仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "合肥-杭州",
      "distance": 480,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 4,
      "from_house_id": 1,
      "from_house_name": "合肥仓",
      "to_house_id": 10,
      "to_house_name": "武汉仓",
      "line_name": "合肥-武汉",
      "distance": 450,
      "efficiency_original": 9.0,
      "efficiency": 0.375,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 5,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "南京-郑州",
      "distance": 650,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 6,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "南京-杭州",
      "distance": 340,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "3,5,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 7,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 5,
      "to_house_name": "济南仓",
      "line_name": "南京-济南",
      "distance": 670,
      "efficiency_original": 14.0,
      "efficiency": 0.583,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 8,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 1,
      "to_house_name": "合肥仓",
      "line_name": "南京-合肥",
      "distance": 170,
      "efficiency_original": 4.0,
      "efficiency": 0.167,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 9,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 6,
      "to_house_name": "上海仓",
      "line_name": "南京-上海",
      "distance": 350,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 10,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 10,
      "to_house_name": "武汉仓",
      "line_name": "南京-武汉",
      "distance": 580,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 11,
      "from_house_id": 2,
      "from_house_name": "南京仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "南京-天津",
      "distance": 980,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 12,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "济南-天津",
      "distance": 370,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 13,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "济南-郑州",
      "distance": 480,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 14,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "济南-南京",
      "distance": 670,
      "efficiency_original": 13.0,
      "efficiency": 0.542,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 15,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 14,
      "to_house_name": "石家庄仓",
      "line_name": "济南-石家庄",
      "distance": 300,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "3,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 16,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 11,
      "to_house_name": "长沙仓",
      "line_name": "济南-长沙",
      "distance": 1207,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "4,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 17,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 7,
      "to_house_name": "西安仓",
      "line_name": "济南-西安",
      "distance": 953,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "4,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 18,
      "from_house_id": 5,
      "from_house_name": "济南仓",
      "to_house_id": 9,
      "to_house_name": "沈阳仓",
      "line_name": "济南-沈阳",
      "distance": 971,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "1,4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 19,
      "from_house_id": 4,
      "from_house_name": "杭州仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "杭州-南京",
      "distance": 340,
      "efficiency_original": 11.0,
      "efficiency": 0.458,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 20,
      "from_house_id": 4,
      "from_house_name": "杭州仓",
      "to_house_id": 18,
      "to_house_name": "泉州仓",
      "line_name": "杭州-泉州",
      "distance": 850,
      "efficiency_original": 17.0,
      "efficiency": 0.708,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 21,
      "from_house_id": 4,
      "from_house_name": "杭州仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "杭州-郑州",
      "distance": 960,
      "efficiency_original": 20.0,
      "efficiency": 0.833,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 22,
      "from_house_id": 4,
      "from_house_name": "杭州仓",
      "to_house_id": 11,
      "to_house_name": "长沙仓",
      "line_name": "杭州-长沙",
      "distance": 960,
      "efficiency_original": 34.0,
      "efficiency": 1.417,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 23,
      "from_house_id": 4,
      "from_house_name": "杭州仓",
      "to_house_id": 6,
      "to_house_name": "上海仓",
      "line_name": "杭州-上海",
      "distance": 100,
      "efficiency_original": 2.0,
      "efficiency": 0.083,
      "time_unit": "hours",
      "shipping_days": "3,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 24,
      "from_house_id": 6,
      "from_house_name": "上海仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "上海-南京",
      "distance": 350,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 25,
      "from_house_id": 6,
      "from_house_name": "上海仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "上海-杭州",
      "distance": 100,
      "efficiency_original": 2.0,
      "efficiency": 0.083,
      "time_unit": "hours",
      "shipping_days": "7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 26,
      "from_house_id": 6,
      "from_house_name": "上海仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "上海-郑州",
      "distance": 950,
      "efficiency_original": 19.0,
      "efficiency": 0.792,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 27,
      "from_house_id": 7,
      "from_house_name": "西安仓",
      "to_house_id": 15,
      "to_house_name": "乌鲁木齐仓",
      "line_name": "西安-乌鲁木齐",
      "distance": 2700,
      "efficiency_original": 66.0,
      "efficiency": 2.75,
      "time_unit": "hours",
      "shipping_days": "6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 28,
      "from_house_id": 7,
      "from_house_name": "西安仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "西安-郑州",
      "distance": 550,
      "efficiency_original": 13.0,
      "efficiency": 0.542,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 29,
      "from_house_id": 7,
      "from_house_name": "西安仓",
      "to_house_id": 16,
      "to_house_name": "太原仓",
      "line_name": "西安-太原",
      "distance": 650,
      "efficiency_original": 13.0,
      "efficiency": 0.542,
      "time_unit": "hours",
      "shipping_days": "3,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 30,
      "from_house_id": 7,
      "from_house_name": "西安仓",
      "to_house_id": 13,
      "to_house_name": "成都仓",
      "line_name": "西安-成都",
      "distance": 750,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "3,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 31,
      "from_house_id": 7,
      "from_house_name": "西安仓",
      "to_house_id": 20,
      "to_house_name": "兰州仓",
      "line_name": "西安-兰州",
      "distance": 600,
      "efficiency_original": 13.0,
      "efficiency": 0.542,
      "time_unit": "hours",
      "shipping_days": "3,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 32,
      "from_house_id": 16,
      "from_house_name": "太原仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "太原-郑州",
      "distance": 600,
      "efficiency_original": 11.0,
      "efficiency": 0.458,
      "time_unit": "hours",
      "shipping_days": "2,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 33,
      "from_house_id": 16,
      "from_house_name": "太原仓",
      "to_house_id": 7,
      "to_house_name": "西安仓",
      "line_name": "太原-西安",
      "distance": 650,
      "efficiency_original": 11.0,
      "efficiency": 0.458,
      "time_unit": "hours",
      "shipping_days": "2,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 34,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 5,
      "to_house_name": "济南仓",
      "line_name": "天津-济南",
      "distance": 370,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 35,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "天津-郑州",
      "distance": 710,
      "efficiency_original": 15.5,
      "efficiency": 0.646,
      "time_unit": "hours",
      "shipping_days": "1,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 36,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 9,
      "to_house_name": "沈阳仓",
      "line_name": "天津-沈阳",
      "distance": 720,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "2,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 37,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 17,
      "to_house_name": "哈尔滨仓",
      "line_name": "天津-哈尔滨",
      "distance": 1250,
      "efficiency_original": 36.0,
      "efficiency": 1.5,
      "time_unit": "hours",
      "shipping_days": "2,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 38,
      "from_house_id": 9,
      "from_house_name": "沈阳仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "沈阳-天津",
      "distance": 720,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 39,
      "from_house_id": 9,
      "from_house_name": "沈阳仓",
      "to_house_id": 17,
      "to_house_name": "哈尔滨仓",
      "line_name": "沈阳-哈尔滨",
      "distance": 580,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "1,3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 40,
      "from_house_id": 17,
      "from_house_name": "哈尔滨仓",
      "to_house_id": 9,
      "to_house_name": "沈阳仓",
      "line_name": "哈尔滨-沈阳",
      "distance": 580,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 41,
      "from_house_id": 13,
      "from_house_name": "成都仓",
      "to_house_id": 7,
      "to_house_name": "西安仓",
      "line_name": "成都-西安",
      "distance": 750,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 42,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 10,
      "to_house_name": "武汉仓",
      "line_name": "长沙-武汉",
      "distance": 320,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 43,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "长沙-杭州",
      "distance": 1000,
      "efficiency_original": 20.0,
      "efficiency": 0.833,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 44,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "长沙-南京",
      "distance": 860,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 45,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "长沙-郑州",
      "distance": 850,
      "efficiency_original": 17.0,
      "efficiency": 0.708,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 46,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 21,
      "to_house_name": "广州仓",
      "line_name": "长沙-广州",
      "distance": 780,
      "efficiency_original": 16.5,
      "efficiency": 0.688,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 47,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 13,
      "to_house_name": "成都仓",
      "line_name": "长沙-成都",
      "distance": 1150,
      "efficiency_original": 34.0,
      "efficiency": 1.417,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 48,
      "from_house_id": 11,
      "from_house_name": "长沙仓",
      "to_house_id": 19,
      "to_house_name": "南昌仓",
      "line_name": "长沙-南昌",
      "distance": 360,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 49,
      "from_house_id": 10,
      "from_house_name": "武汉仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "武汉-郑州",
      "distance": 550,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 50,
      "from_house_id": 10,
      "from_house_name": "武汉仓",
      "to_house_id": 11,
      "to_house_name": "长沙仓",
      "line_name": "武汉-长沙",
      "distance": 320,
      "efficiency_original": 19.0,
      "efficiency": 0.792,
      "time_unit": "hours",
      "shipping_days": "3,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 51,
      "from_house_id": 10,
      "from_house_name": "武汉仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "武汉-南京",
      "distance": 620,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 52,
      "from_house_id": 10,
      "from_house_name": "武汉仓",
      "to_house_id": 1,
      "to_house_name": "合肥仓",
      "line_name": "武汉-合肥",
      "distance": 450,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 53,
      "from_house_id": 10,
      "from_house_name": "武汉仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "武汉-杭州",
      "distance": 800,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "3",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 54,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 11,
      "to_house_name": "长沙仓",
      "line_name": "郑州-长沙",
      "distance": 850,
      "efficiency_original": 23.0,
      "efficiency": 0.958,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 55,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 10,
      "to_house_name": "武汉仓",
      "line_name": "郑州-武汉",
      "distance": 550,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 56,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "郑州-杭州",
      "distance": 960,
      "efficiency_original": 18.0,
      "efficiency": 0.75,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 57,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 1,
      "to_house_name": "合肥仓",
      "line_name": "郑州-合肥",
      "distance": 540,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 58,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 21,
      "to_house_name": "广州仓",
      "line_name": "郑州-广州",
      "distance": 1550,
      "efficiency_original": 45.0,
      "efficiency": 1.875,
      "time_unit": "hours",
      "shipping_days": "2,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 59,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 18,
      "to_house_name": "泉州仓",
      "line_name": "郑州-泉州",
      "distance": 1450,
      "efficiency_original": 48.0,
      "efficiency": 2.0,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 60,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "郑州-南京",
      "distance": 650,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 61,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 7,
      "to_house_name": "西安仓",
      "line_name": "郑州-西安",
      "distance": 550,
      "efficiency_original": 13.0,
      "efficiency": 0.542,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 62,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 16,
      "to_house_name": "太原仓",
      "line_name": "郑州-太原",
      "distance": 448,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 63,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "郑州-天津",
      "distance": 710,
      "efficiency_original": 15.0,
      "efficiency": 0.625,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 64,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 5,
      "to_house_name": "济南仓",
      "line_name": "郑州-济南",
      "distance": 480,
      "efficiency_original": 12.0,
      "efficiency": 0.5,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 65,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 9,
      "to_house_name": "沈阳仓",
      "line_name": "郑州-沈阳",
      "distance": 1400,
      "efficiency_original": 48.0,
      "efficiency": 2.0,
      "time_unit": "hours",
      "shipping_days": "2",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 66,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 6,
      "to_house_name": "上海仓",
      "line_name": "郑州-上海",
      "distance": 950,
      "efficiency_original": 24.0,
      "efficiency": 1.0,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 67,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 13,
      "to_house_name": "成都仓",
      "line_name": "郑州-成都",
      "distance": 1200,
      "efficiency_original": 36.0,
      "efficiency": 1.5,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 68,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 19,
      "to_house_name": "南昌仓",
      "line_name": "郑州-南昌",
      "distance": 840,
      "efficiency_original": 18.0,
      "efficiency": 0.75,
      "time_unit": "hours",
      "shipping_days": "4,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 69,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 20,
      "to_house_name": "兰州仓",
      "line_name": "郑州-兰州",
      "distance": 1140,
      "efficiency_original": 33.0,
      "efficiency": 1.375,
      "time_unit": "hours",
      "shipping_days": "2,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 70,
      "from_house_id": 21,
      "from_house_name": "广州仓",
      "to_house_id": 11,
      "to_house_name": "长沙仓",
      "line_name": "广州-长沙",
      "distance": 780,
      "efficiency_original": 16.0,
      "efficiency": 0.667,
      "time_unit": "hours",
      "shipping_days": "5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 71,
      "from_house_id": 18,
      "from_house_name": "泉州仓",
      "to_house_id": 4,
      "to_house_name": "杭州仓",
      "line_name": "泉州-杭州",
      "distance": 850,
      "efficiency_original": 17.0,
      "efficiency": 0.708,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 72,
      "from_house_id": 18,
      "from_house_name": "泉州仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "泉州-郑州",
      "distance": 1450,
      "efficiency_original": 46.0,
      "efficiency": 1.917,
      "time_unit": "hours",
      "shipping_days": "4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 73,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 14,
      "to_house_name": "石家庄仓",
      "line_name": "郑州-石家庄",
      "distance": 410,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "1,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 74,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 14,
      "to_house_name": "石家庄仓",
      "line_name": "天津-石家庄",
      "distance": 300,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "1,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 75,
      "from_house_id": 14,
      "from_house_name": "石家庄仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "石家庄-天津",
      "distance": 300,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 76,
      "from_house_id": 3,
      "from_house_name": "郑州仓",
      "to_house_id": 12,
      "to_house_name": "北京仓",
      "line_name": "郑州-北京",
      "distance": 700,
      "efficiency_original": 23.0,
      "efficiency": 0.958,
      "time_unit": "hours",
      "shipping_days": "1,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 77,
      "from_house_id": 14,
      "from_house_name": "石家庄仓",
      "to_house_id": 12,
      "to_house_name": "北京仓",
      "line_name": "石家庄-北京",
      "distance": 310,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "1,5",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 78,
      "from_house_id": 12,
      "from_house_name": "北京仓",
      "to_house_id": 14,
      "to_house_name": "石家庄仓",
      "line_name": "北京-石家庄",
      "distance": 310,
      "efficiency_original": 10.0,
      "efficiency": 0.417,
      "time_unit": "hours",
      "shipping_days": "2,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 79,
      "from_house_id": 12,
      "from_house_name": "北京仓",
      "to_house_id": 8,
      "to_house_name": "天津仓",
      "line_name": "北京-天津",
      "distance": 60,
      "efficiency_original": 2.0,
      "efficiency": 0.083,
      "time_unit": "hours",
      "shipping_days": "1,2,3,4,5,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 80,
      "from_house_id": 12,
      "from_house_name": "北京仓",
      "to_house_id": 9,
      "to_house_name": "沈阳仓",
      "line_name": "北京-沈阳",
      "distance": 700,
      "efficiency_original": 23.0,
      "efficiency": 0.958,
      "time_unit": "hours",
      "shipping_days": "3,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 81,
      "from_house_id": 12,
      "from_house_name": "北京仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "北京-郑州",
      "distance": 700,
      "efficiency_original": 16.0,
      "efficiency": 0.667,
      "time_unit": "hours",
      "shipping_days": "1,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 82,
      "from_house_id": 12,
      "from_house_name": "北京仓",
      "to_house_id": 5,
      "to_house_name": "济南仓",
      "line_name": "北京-济南",
      "distance": 390,
      "efficiency_original": 8.0,
      "efficiency": 0.333,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 83,
      "from_house_id": 8,
      "from_house_name": "天津仓",
      "to_house_id": 12,
      "to_house_name": "北京仓",
      "line_name": "天津-北京",
      "distance": 60,
      "efficiency_original": 2.0,
      "efficiency": 0.083,
      "time_unit": "hours",
      "shipping_days": "1,2,3,4,5,6",
      "transport_mode": "往返",
      "line_type": "调拨"
    },
    {
      "id": 84,
      "from_house_id": 13,
      "from_house_name": "成都仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "成都-郑州",
      "distance": 1273,
      "efficiency_original": 35.0,
      "efficiency": 1.458,
      "time_unit": "hours",
      "shipping_days": "1,3,5",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 85,
      "from_house_id": 13,
      "from_house_name": "成都仓",
      "to_house_id": 2,
      "to_house_name": "南京仓",
      "line_name": "成都-南京",
      "distance": 1661,
      "efficiency_original": 39.0,
      "efficiency": 1.625,
      "time_unit": "hours",
      "shipping_days": "2,4,6",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 86,
      "from_house_id": 22,
      "from_house_name": "鹿邑仓",
      "to_house_id": 3,
      "to_house_name": "郑州仓",
      "line_name": "鹿邑-郑州",
      "distance": 260,
      "efficiency_original": 6.0,
      "efficiency": 0.25,
      "time_unit": "hours",
      "shipping_days": "2,4",
      "transport_mode": "单程",
      "line_type": "调拨"
    },
    {
      "id": 87,
      "from_house_id": 9,
      "from_house_name": "沈阳仓",
      "to_house_id": 23,
      "to_house_name": "长春仓",
      "line_name": "沈阳-长春",
      "distance": 320,
      "efficiency_original": 7.0,
      "efficiency": 0.292,
      "time_unit": "hours",
      "shipping_days": "3,4,6,7",
      "transport_mode": "单程",
      "line_type": "调拨"
    }
  ]
}

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)