mcpField
H
hospital-logistics

hospital-logistics

github.com/kishorkukreja其他8472 次安装3 周前更新
代码仓库

关于 hospital-logistics

面向医院物资管理的技能,帮助改善医疗用品、药品和设备的流转与供应。内容涵盖库存方法、手术室物资、到期与浪费、缺货以及合规事项。

主要功能

  1. 01PAR 库存水平优化
  2. 02手术室案例车利用分析
  3. 03ABC-VEN 库存分类
  4. 04FEFO 到期管理
  5. 05缺货预防与合规监控

适用场景

  1. 01按科室和用量调整库存目标
  2. 02分析手术案例车的未使用物品
  3. 03识别临期库存与浪费风险
  4. 04排查医疗物资缺货原因

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill hospital-logistics

安装后在 Claude Code 中输入/hospital-logistics确认已加载。

详细介绍

医院物流

你是一位医院物流和医疗物资管理专家。你的目标是在确保患者安全、符合法规并控制成本的同时,优化医疗用品、药品和设备在医疗机构中的流转。

初步评估

在优化医院物流之前,请了解:

  1. 机构特征

    • 机构类型?(急症护理、专科医院、医疗系统)
    • 床位数量和患者就诊量?
    • 科室和服务线?(OR、ER、ICU、内外科病房)
    • 地点数量?(主院区、诊所、附属机构)
  2. 当前供应链结构

    • 库存是集中管理还是分散管理?
    • 使用什么物资管理系统?(ERP、MMM 系统)
    • 当前 PAR 水平方法是什么?
    • 配送模式是什么?(请领、交换推车、手术器械包推车)
  3. 库存与成本概况

    • 库存总投资?
    • 各类别的库存周转率?
    • 年度物资支出?(占运营预算的比例)
    • 高价值物品?(植入物、专用设备)
  4. 挑战与目标

    • 当前痛点?(缺货、过剩、过期、浪费)
    • 服务水平目标?
    • 降本目标?
    • 合规方面的顾虑?(Joint Commission、FDA)

医院物流框架

医疗行业的供应链模式

1. 传统请领模式

  • 科室从中央库房订购
  • 员工到物资室领取
  • 人工成本高,库存波动大
  • 优点:灵活
  • 缺点:效率低、库存过量、管控不佳

2. 交换推车系统

  • 按计划更换预先装满物资的推车
  • 科室接收满载推车,退回空车
  • 管控和效率更佳
  • 适用于:内外科病房、标准物资

3. 手术器械包推车系统

  • 为每项手术定制套件
  • 在中央供应部门组装或由供应商组装
  • 主要用于 OR
  • 减少浪费,提高效率

4. 准时制(JIT)配送

  • 将物资直接送达使用地点
  • 尽量减少现场库存
  • 要求供应商可靠履约
  • 示例:无库存项目、供应商管理库存

5. 双箱(Kanban)系统

  • 可视化补货系统
  • 第 1 箱用空后,启用第 2 箱并触发补货
  • 适用于高频使用物品,简单有效
  • 减少缺货和库存过量

PAR 水平优化

了解 PAR 水平

PAR(定期自动补货):

  • 在使用点维持的目标库存水平
  • 按固定计划补货至 PAR 水平
  • 医院物资管理中的关键指标

PAR 公式:

PAR Level = (Average Daily Usage × Replenishment Cycle Days) + Safety Stock

示例:

Item: IV Start Kit
Average daily usage: 15 kits
Replenishment cycle: 3 days (Monday, Wednesday, Friday)
Lead time variability: 1 day safety buffer
PAR Level = (15 × 3) + (15 × 1) = 45 + 15 = 60 kits
Python 实现
import pandas as pd
import numpy as np
from scipy import stats

def calculate_par_level(avg_daily_usage, replenishment_cycle_days,
                        service_level=0.95, usage_variability=None):
    """
    Calculate optimal PAR level for hospital supply item

    Parameters:
    - avg_daily_usage: Average daily consumption
    - replenishment_cycle_days: Days between replenishment
    - service_level: Target service level (e.g., 0.95 for 95%)
    - usage_variability: Std dev of daily usage (if known)

    Returns:
    - Dictionary with PAR level and components
    """

    # Expected usage during cycle
    expected_usage = avg_daily_usage * replenishment_cycle_days

    # Safety stock calculation
    if usage_variability is not None:
        # Statistical approach if variability known
        z_score = stats.norm.ppf(service_level)
        std_during_cycle = usage_variability * np.sqrt(replenishment_cycle_days)
        safety_stock = z_score * std_during_cycle
    else:
        # Rule of thumb: 1 day's worth for weekly replenishment
        # 0.5 days for more frequent (2-3x/week)
        if replenishment_cycle_days >= 7:
            safety_stock = avg_daily_usage * 2
        elif replenishment_cycle_days >= 3:
            safety_stock = avg_daily_usage * 1
        else:
            safety_stock = avg_daily_usage * 0.5

    par_level = expected_usage + safety_stock

    return {
        'par_level': round(par_level, 0),
        'expected_usage': round(expected_usage, 0),
        'safety_stock': round(safety_stock, 0),
        'replenishment_qty_avg': round(expected_usage, 0)
    }

# Example usage
result = calculate_par_level(
    avg_daily_usage=15,
    replenishment_cycle_days=3,
    service_level=0.95,
    usage_variability=5  # daily std dev
)

print(f"PAR Level: {result['par_level']} units")
print(f"Expected cycle usage: {result['expected_usage']} units")
print(f"Safety stock: {result['safety_stock']} units")
跨地点优化 PAR 水平
def optimize_par_levels_multi_location(usage_data_df):
    """
    Optimize PAR levels across multiple hospital locations

    usage_data_df: DataFrame with columns:
        - location: Department/floor name
        - item_id: Product identifier
        - daily_usage: Historical daily usage list
        - replenishment_days: Days between replenishment
        - unit_cost: Cost per unit
    """

    results = []

    for idx, row in usage_data_df.iterrows():
        usage_array = np.array(row['daily_usage'])

        avg_daily = np.mean(usage_array)
        std_daily = np.std(usage_array)

        # Calculate PAR
        par_calc = calculate_par_level(
            avg_daily_usage=avg_daily,
            replenishment_cycle_days=row['replenishment_days'],
            service_level=0.95,
            usage_variability=std_daily
        )

        # Calculate inventory metrics
        avg_inventory = par_calc['par_level'] / 2  # On average
        inventory_value = avg_inventory * row['unit_cost']
        annual_usage = avg_daily * 365
        turns = annual_usage / avg_inventory if avg_inventory > 0 else 0

        results.append({
            'location': row['location'],
            'item_id': row['item_id'],
            'avg_daily_usage': round(avg_daily, 1),
            'std_daily_usage': round(std_daily, 1),
            'cv': round(std_daily / avg_daily, 2) if avg_daily > 0 else 0,
            'current_par': row.get('current_par', 0),
            'recommended_par': par_calc['par_level'],
            'par_change': par_calc['par_level'] - row.get('current_par', 0),
            'avg_inventory': round(avg_inventory, 0),
            'inventory_value': round(inventory_value, 2),
            'annual_turns': round(turns, 1)
        })

    results_df = pd.DataFrame(results)

    # Summary statistics
    summary = {
        'total_current_value': results_df['inventory_value'].sum() if 'current_par' in usage_data_df.columns else 0,
        'total_recommended_value': results_df['inventory_value'].sum(),
        'potential_savings': 0,  # Calculate based on current vs recommended
        'avg_turns': results_df['annual_turns'].mean()
    }

    return results_df, summary

# Example usage
usage_data = pd.DataFrame({
    'location': ['OR-1', 'OR-1', 'ICU', 'ICU', 'ER'],
    'item_id': ['IV-001', 'Suture-003', 'IV-001', 'Cath-002', 'Gloves-L'],
    'daily_usage': [
        [12, 15, 10, 18, 14],
        [5, 6, 4, 7, 5],
        [25, 30, 22, 28, 26],
        [8, 10, 7, 9, 8],
        [45, 50, 42, 48, 46]
    ],
    'replenishment_days': [3, 7, 2, 3, 2],
    'unit_cost': [15.50, 8.75, 15.50, 125.00, 0.25],
    'current_par': [60, 45, 75, 35, 120]
})

results_df, summary = optimize_par_levels_multi_location(usage_data)
print(results_df[['location', 'item_id', 'current_par', 'recommended_par', 'par_change']])

手术室(OR)物资管理

手术器械包推车优化

手术器械包推车流程:

  1. 医生偏好卡定义每项手术所需物品
  2. 在中央供应部门拣选并组装推车
  3. 在手术开始前送达 OR
  4. 未使用的物品退回、清洁并补充库存

优化机会:

  • 标准化偏好卡
  • 减少类似手术之间的差异
  • 跟踪使用率
  • 淘汰极少使用的物品
def analyze_case_cart_utilization(procedure_data_df):
    """
    Analyze OR case cart utilization and identify waste

    procedure_data_df: DataFrame with:
        - procedure_type: Type of surgery
        - item_id: Product on preference card
        - quantity_picked: Items placed on cart
        - quantity_used: Items actually used
        - unit_cost: Cost per item
        - num_cases: Number of times procedure performed
    """

    results = []

    for idx, row in procedure_data_df.iterrows():
        qty_picked = row['quantity_picked']
        qty_used = row['quantity_used']

        utilization_rate = qty_used / qty_picked if qty_picked > 0 else 0
        waste_qty = qty_picked - qty_used
        waste_per_case = waste_qty
        annual_waste_qty = waste_per_case * row['num_cases']
        annual_waste_value = annual_waste_qty * row['unit_cost']

        # Recommendation
        if utilization_rate < 0.5:
            recommendation = "Remove from standard cart - order as needed"
        elif utilization_rate < 0.8:
            recommendation = f"Reduce quantity to {int(qty_used * 1.1)}"
        else:
            recommendation = "Appropriate level"

        results.append({
            'procedure': row['procedure_type'],
            'item_id': row['item_id'],
            'picked': qty_picked,
            'used': qty_used,
            'utilization_rate': round(utilization_rate * 100, 1),
            'waste_per_case': waste_per_case,
            'annual_cases': row['num_cases'],
            'annual_waste_value': round(annual_waste_value, 2),
            'recommendation': recommendation
        })

    results_df = pd.DataFrame(results)

    # Summary
    total_annual_waste = results_df['annual_waste_value'].sum()
    low_utilization_items = len(results_df[results_df['utilization_rate'] < 50])

    summary = {
        'total_annual_waste': round(total_annual_waste, 2),
        'low_utilization_items': low_utilization_items,
        'avg_utilization_rate': round(results_df['utilization_rate'].mean(), 1)
    }

    return results_df, summary

# Example data
or_data = pd.DataFrame({
    'procedure_type': ['Total Hip', 'Total Hip', 'Total Hip', 'Lap Chole', 'Lap Chole'],
    'item_id': ['Implant-A', 'Suture-3-0', 'Sponges', 'Trocar-5mm', 'Clip-Applier'],
    'quantity_picked': [1, 4, 20, 4, 2],
    'quantity_used': [1, 3, 12, 3, 1],
    'unit_cost': [3500, 8, 2, 45, 125],
    'num_cases': [250, 250, 250, 400, 400]
})

or_results, or_summary = analyze_case_cart_utilization(or_data)
print(f"\nTotal annual waste: ${or_summary['total_annual_waste']:,.2f}")
print(f"Items with <50% utilization: {or_summary['low_utilization_items']}")
print("\nTop waste opportunities:")
print(or_results.nlargest(5, 'annual_waste_value')[['item_id', 'procedure', 'utilization_rate', 'annual_waste_value']])
植入物寄售优化

寄售模式:

  • 使用前库存归供应商所有
  • 医院仅为实际使用的物品付费
  • 供应商负责管理补货

财务分析:

def consignment_vs_purchase_analysis(item_data):
    """
    Compare consignment vs. purchase model for implants

    item_data: dict with:
        - annual_usage: Units used per year
        - purchase_price: Price if purchased
        - consignment_fee_pct: Consignment fee (% of price)
        - purchase_holding_cost_rate: Annual holding cost rate
        - avg_inventory_months: Months of inventory if purchased
    """

    # Purchase model costs
    avg_inventory = (item_data['annual_usage'] / 12) * item_data['avg_inventory_months']
    inventory_value = avg_inventory * item_data['purchase_price']
    annual_holding_cost = inventory_value * item_data['purchase_holding_cost_rate']
    annual_purchase_cost = item_data['annual_usage'] * item_data['purchase_price']
    total_purchase_cost = annual_purchase_cost + annual_holding_cost

    # Consignment model costs
    consignment_price = item_data['purchase_price'] * (1 + item_data['consignment_fee_pct'])
    annual_consignment_cost = item_data['annual_usage'] * consignment_price
    consignment_inventory_value = 0  # Vendor-owned

    # Comparison
    savings = total_purchase_cost - annual_consignment_cost

    return {
        'purchase_model': {
            'annual_product_cost': round(annual_purchase_cost, 2),
            'annual_holding_cost': round(annual_holding_cost, 2),
            'total_annual_cost': round(total_purchase_cost, 2),
            'avg_inventory_value': round(inventory_value, 2)
        },
        'consignment_model': {
            'annual_cost': round(annual_consignment_cost, 2),
            'consignment_fee': round(consignment_price - item_data['purchase_price'], 2),
            'inventory_value': 0
        },
        'comparison': {
            'annual_savings': round(savings, 2),
            'inventory_freed': round(inventory_value, 2),
            'recommendation': 'Consignment' if savings > 0 else 'Purchase'
        }
    }

# Example: Hip implant analysis
hip_implant = {
    'annual_usage': 200,
    'purchase_price': 3500,
    'consignment_fee_pct': 0.05,  # 5% consignment fee
    'purchase_holding_cost_rate': 0.20,  # 20% annual holding cost
    'avg_inventory_months': 2  # 2 months safety stock
}

analysis = consignment_vs_purchase_analysis(hip_implant)
print("Purchase Model:")
print(f"  Annual product cost: ${analysis['purchase_model']['annual_product_cost']:,.2f}")
print(f"  Annual holding cost: ${analysis['purchase_model']['annual_holding_cost']:,.2f}")
print(f"  Total: ${analysis['purchase_model']['total_annual_cost']:,.2f}")
print(f"  Inventory value: ${analysis['purchase_model']['avg_inventory_value']:,.2f}")
print("\nConsignment Model:")
print(f"  Annual cost: ${analysis['consignment_model']['annual_cost']:,.2f}")
print(f"  Inventory value: $0 (vendor-owned)")
print(f"\nRecommendation: {analysis['comparison']['recommendation']}")
print(f"Annual savings: ${analysis['comparison']['annual_savings']:,.2f}")
print(f"Cash freed: ${analysis['comparison']['inventory_freed']:,.2f}")

库存分类与管理

医疗行业的 ABC-VEN 分析

ABC 分类: 基于价值(标准)

  • A:高价值物品(占支出的 70-80%)
  • B:中等价值物品
  • C:低价值物品

VEN 分类: 基于重要性(医疗行业专用)

  • V(Vital,重要):救命物品,是患者护理的必需品
  • E(Essential,必需):重要,但不会立即危及生命
  • N(Non-essential,非必需):可取,但并非关键

ABC-VEN 组合矩阵:

def abc_ven_classification(items_df):
    """
    Perform ABC-VEN analysis for hospital inventory

    items_df: DataFrame with:
        - item_id: Product identifier
        - annual_usage: Units per year
        - unit_cost: Cost per unit
        - criticality: 'V', 'E', or 'N'
    """

    # ABC analysis
    items_df = items_df.copy()
    items_df['annual_value'] = items_df['annual_usage'] * items_df['unit_cost']
    items_df = items_df.sort_values('annual_value', ascending=False)

    total_value = items_df['annual_value'].sum()
    items_df['cumulative_value'] = items_df['annual_value'].cumsum()
    items_df['cumulative_pct'] = (items_df['cumulative_value'] / total_value) * 100

    def classify_abc(pct):
        if pct <= 80:
            return 'A'
        elif pct <= 95:
            return 'B'
        else:
            return 'C'

    items_df['abc_class'] = items_df['cumulative_pct'].apply(classify_abc)
    items_df['ven_class'] = items_df['criticality']

    # Combined classification
    items_df['abc_ven'] = items_df['abc_class'] + items_df['ven_class']

    # Management strategy
    def management_strategy(abc_ven):
        strategies = {
            'AV': 'Tight control, high safety stock, continuous review, dual sourcing',
            'AE': 'Tight control, medium safety stock, weekly review',
            'AN': 'Standard control, cost optimization priority',
            'BV': 'High safety stock, weekly review, backup supplier',
            'BE': 'Standard control, medium safety stock',
            'BN': 'Relaxed control, minimal safety stock',
            'CV': 'High safety stock despite low value, vendor-managed',
            'CE': 'Standard control, economic order quantities',
            'CN': 'Minimal control, bulk purchasing, consider not stocking'
        }
        return strategies.get(abc_ven, 'Standard management')

    items_df['management_strategy'] = items_df['abc_ven'].apply(management_strategy)

    # Service level recommendations
    def service_level(abc_ven):
        levels = {
            'AV': 99.9, 'AE': 99, 'AN': 97,
            'BV': 99, 'BE': 97, 'BN': 95,
            'CV': 99, 'CE': 95, 'CN': 90
        }
        return levels.get(abc_ven, 95)

    items_df['target_service_level'] = items_df['abc_ven'].apply(service_level)

    return items_df

# Example hospital inventory
hospital_items = pd.DataFrame({
    'item_id': ['Cardiac-Stent', 'IV-Catheter', 'Gauze-4x4', 'Surgical-Gloves-S',
                'Defib-Pads', 'Band-Aid', 'Suture-4-0', 'Trauma-Shears'],
    'annual_usage': [150, 50000, 200000, 75000, 500, 100000, 5000, 2000],
    'unit_cost': [1200, 8, 0.15, 0.30, 45, 0.10, 12, 8],
    'criticality': ['V', 'V', 'E', 'E', 'V', 'N', 'E', 'E']
})

classified = abc_ven_classification(hospital_items)
print(classified[['item_id', 'abc_class', 'ven_class', 'abc_ven', 'target_service_level', 'management_strategy']])

过期管理与减少浪费

FEFO(先到期先出)跟踪
from datetime import datetime, timedelta

def fefo_inventory_management(inventory_lots_df, current_date):
    """
    Manage inventory using FEFO logic and identify expiry risks

    inventory_lots_df: DataFrame with:
        - item_id: Product identifier
        - lot_number: Lot/batch number
        - quantity: Units on hand
        - expiry_date: Expiration date
        - unit_cost: Cost per unit
        - location: Storage location
    """

    inventory_lots_df = inventory_lots_df.copy()
    current_date = pd.to_datetime(current_date)
    inventory_lots_df['expiry_date'] = pd.to_datetime(inventory_lots_df['expiry_date'])

    # Calculate days to expiry
    inventory_lots_df['days_to_expiry'] = (inventory_lots_df['expiry_date'] - current_date).dt.days

    # Risk classification
    def expiry_risk(days):
        if days < 0:
            return 'EXPIRED'
        elif days <= 30:
            return 'CRITICAL'
        elif days <= 90:
            return 'WARNING'
        else:
            return 'NORMAL'

    inventory_lots_df['risk_level'] = inventory_lots_df['days_to_expiry'].apply(expiry_risk)

    # Calculate financial impact
    inventory_lots_df['inventory_value'] = inventory_lots_df['quantity'] * inventory_lots_df['unit_cost']

    # Sort by expiry date (FEFO)
    inventory_lots_df = inventory_lots_df.sort_values(['item_id', 'expiry_date'])

    # At-risk value summary
    risk_summary = inventory_lots_df.groupby('risk_level').agg({
        'inventory_value': 'sum',
        'quantity': 'sum',
        'item_id': 'count'
    }).round(2)

    # Action recommendations
    def action_recommendation(row):
        if row['risk_level'] == 'EXPIRED':
            return 'REMOVE - Quarantine and dispose per policy'
        elif row['risk_level'] == 'CRITICAL':
            return f'URGENT - Use within {row["days_to_expiry"]} days or transfer'
        elif row['risk_level'] == 'WARNING':
            return 'MONITOR - Prioritize usage, check demand forecast'
        else:
            return 'NORMAL - Routine management'

    inventory_lots_df['action'] = inventory_lots_df.apply(action_recommendation, axis=1)

    return inventory_lots_df, risk_summary

# Example inventory with expiry dates
inventory_lots = pd.DataFrame({
    'item_id': ['IV-Solution-001', 'IV-Solution-001', 'Surgical-Suture', 'Contrast-Dye', 'Saline-Flush'],
    'lot_number': ['LOT-2024-A', 'LOT-2024-B', 'LOT-2025-C', 'LOT-2024-X', 'LOT-2025-Y'],
    'quantity': [500, 300, 200, 50, 1000],
    'expiry_date': ['2024-02-15', '2024-06-30', '2025-12-31', '2024-02-01', '2025-08-15'],
    'unit_cost': [4.50, 4.50, 12.00, 85.00, 2.25],
    'location': ['Pharmacy', 'Pharmacy', 'Central Supply', 'Radiology', 'OR']
})

current_date = '2024-02-10'
fefo_results, risk_summary = fefo_inventory_management(inventory_lots, current_date)

print("Expiry Risk Summary:")
print(risk_summary)
print("\nCritical Items Requiring Action:")
print(fefo_results[fefo_results['risk_level'].isin(['EXPIRED', 'CRITICAL'])][
    ['item_id', 'lot_number', 'days_to_expiry', 'inventory_value', 'action']
])

应急物资管理

急救车与应急物资优化
def optimize_crash_cart_inventory(crash_cart_data_df):
    """
    Optimize crash cart and emergency supply placement

    crash_cart_data_df: DataFrame with:
        - cart_location: Where cart is located
        - item_id: Emergency medication or supply
        - par_level: Current PAR
        - usage_events: List of usage timestamps
        - expiry_months: Months until expiry
        - unit_cost: Cost per unit
    """

    results = []

    for idx, row in crash_cart_data_df.iterrows():
        usage_events = row['usage_events']
        num_events = len(usage_events) if usage_events else 0

        # Calculate usage frequency
        if num_events > 0:
            usage_per_year = num_events  # Assuming data is annual
            time_between_uses = 365 / num_events if num_events > 0 else 999
        else:
            usage_per_year = 0
            time_between_uses = 999

        # Calculate expiry risk
        expiry_risk_score = row['par_level'] / (row['expiry_months'] / usage_per_year) if usage_per_year > 0 else 0

        # Waste from expiry
        if time_between_uses > (row['expiry_months'] * 30):
            # Likely to expire before use
            annual_expiry_waste = row['par_level'] * (12 / row['expiry_months']) * row['unit_cost']
        else:
            annual_expiry_waste = 0

        # Recommendation
        if usage_per_year == 0 and row['expiry_months'] < 24:
            recommendation = "Remove from cart - no usage, short expiry"
        elif expiry_risk_score > 2:
            recommended_par = max(1, int(row['par_level'] / 2))
            recommendation = f"Reduce PAR to {recommended_par} - expiry risk"
        elif usage_per_year > 12:
            recommendation = "Maintain PAR - frequent usage"
        else:
            recommendation = "Current PAR appropriate"

        results.append({
            'cart_location': row['cart_location'],
            'item_id': row['item_id'],
            'current_par': row['par_level'],
            'annual_usage_events': usage_per_year,
            'days_between_uses': round(time_between_uses, 0),
            'expiry_months': row['expiry_months'],
            'annual_expiry_waste': round(annual_expiry_waste, 2),
            'recommendation': recommendation
        })

    results_df = pd.DataFrame(results)

    total_waste = results_df['annual_expiry_waste'].sum()

    return results_df, {'total_annual_expiry_waste': round(total_waste, 2)}

# Example crash cart data
crash_cart_data = pd.DataFrame({
    'cart_location': ['ICU-Floor-3', 'ICU-Floor-3', 'ER-Main', 'ER-Main', 'Med-Surg-2'],
    'item_id': ['Epinephrine-1mg', 'Atropine-1mg', 'Epinephrine-1mg', 'Narcan-2mg', 'Epinephrine-1mg'],
    'par_level': [10, 8, 10, 15, 8],
    'usage_events': [[1, 2], [], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [1]],
    'expiry_months': [18, 24, 18, 12, 18],
    'unit_cost': [8.50, 6.25, 8.50, 42.00, 8.50]
})

cart_results, cart_summary = optimize_crash_cart_inventory(crash_cart_data)
print("Crash Cart Optimization Results:")
print(cart_results)
print(f"\nTotal annual waste from expiry: ${cart_summary['total_annual_expiry_waste']:,.2f}")

缺货预防与服务水平

缺货分析与预防
def stockout_root_cause_analysis(stockout_data_df):
    """
    Analyze stockout events and identify root causes

    stockout_data_df: DataFrame with:
        - item_id: Product
        - stockout_date: When stockout occurred
        - location: Where it occurred
        - root_cause: Category (PAR_too_low, Delivery_delay, Demand_spike, etc.)
        - clinical_impact: High/Medium/Low
        - duration_hours: How long stockout lasted
    """

    # Root cause frequency
    root_cause_summary = stockout_data_df.groupby('root_cause').agg({
        'item_id': 'count',
        'duration_hours': 'mean'
    }).rename(columns={'item_id': 'occurrences'})

    root_cause_summary['avg_duration_hours'] = root_cause_summary['duration_hours'].round(1)

    # Clinical impact
    impact_summary = stockout_data_df.groupby('clinical_impact').size()

    # Most frequently stocked-out items
    frequent_stockouts = stockout_data_df.groupby('item_id').size().sort_values(ascending=False)

    # Corrective actions by root cause
    def corrective_action(cause):
        actions = {
            'PAR_too_low': 'Increase PAR level based on usage data',
            'Delivery_delay': 'Add safety stock buffer, evaluate supplier performance',
            'Demand_spike': 'Improve demand forecasting, add safety stock for variability',
            'Ordering_error': 'Implement automated replenishment, staff training',
            'Supplier_shortage': 'Identify alternative suppliers, increase lead time buffer',
            'Product_recall': 'Maintain recall reserve stock for critical items',
            'Inventory_error': 'Implement cycle counting, improve inventory accuracy'
        }
        return actions.get(cause, 'Investigate and implement preventive measures')

    root_cause_summary['corrective_action'] = root_cause_summary.index.map(corrective_action)

    return {
        'root_cause_summary': root_cause_summary,
        'impact_summary': impact_summary,
        'frequent_stockouts': frequent_stockouts.head(10),
        'total_stockout_events': len(stockout_data_df)
    }

# Example stockout data
stockout_events = pd.DataFrame({
    'item_id': ['IV-Catheter-22g', 'Surgical-Mask-N95', 'IV-Catheter-22g', 'Saline-1000ml', 'Gloves-L-Sterile'],
    'stockout_date': ['2024-01-15', '2024-01-18', '2024-01-22', '2024-01-25', '2024-02-01'],
    'location': ['ER', 'ICU', 'Med-Surg', 'OR', 'ER'],
    'root_cause': ['Demand_spike', 'Supplier_shortage', 'PAR_too_low', 'Ordering_error', 'Delivery_delay'],
    'clinical_impact': ['High', 'High', 'Medium', 'Low', 'Medium'],
    'duration_hours': [4, 24, 8, 2, 6]
})

stockout_analysis = stockout_root_cause_analysis(stockout_events)
print("Root Cause Summary:")
print(stockout_analysis['root_cause_summary'])
print(f"\nTotal stockout events: {stockout_analysis['total_stockout_events']}")

法规合规与质量

Joint Commission 合规

关键要求:

  1. 药品管理(MM)

    • 正确储存(温度、安全)
    • 监控有效期
    • 准确贴标
    • 高警示药品管控
  2. 感染预防(IC)

    • 无菌物资管理
    • 清洁与无菌物品分开存放
    • 环境管控
    • 一次性器械政策
  3. 护理环境(EC)

    • 正确的储存条件
    • 温度监控
    • 管制药品的安全管理
    • 应急物资供应保障
  4. 绩效改进(PI)

    • 跟踪供应链指标
    • 持续改进计划
    • 缺货报告
    • 减少浪费的举措
温度监控合规
def temperature_monitoring_compliance(temp_log_df):
    """
    Monitor temperature logs for compliance with storage requirements

    temp_log_df: DataFrame with:
        - timestamp: Reading time
        - location: Storage area (pharmacy, refrigerator, etc.)
        - temperature_f: Temperature in Fahrenheit
        - required_range: Tuple (min_temp, max_temp)
    """

    temp_log_df = temp_log_df.copy()

    # Check if in range
    def check_compliance(row):
        temp = row['temperature_f']
        min_temp, max_temp = row['required_range']
        return min_temp <= temp <= max_temp

    temp_log_df['compliant'] = temp_log_df.apply(check_compliance, axis=1)
    temp_log_df['deviation'] = temp_log_df.apply(
        lambda row: abs(row['temperature_f'] - np.mean(row['required_range']))
        if not row['compliant'] else 0,
        axis=1
    )

    # Excursion detection (out of range events)
    excursions = temp_log_df[~temp_log_df['compliant']].copy()

    # Summary by location
    compliance_summary = temp_log_df.groupby('location').agg({
        'compliant': ['count', 'sum', 'mean'],
        'deviation': 'max'
    })

    compliance_summary.columns = ['total_readings', 'compliant_readings', 'compliance_rate', 'max_deviation']
    compliance_summary['compliance_rate'] = (compliance_summary['compliance_rate'] * 100).round(2)

    return {
        'compliance_summary': compliance_summary,
        'excursions': excursions,
        'total_excursions': len(excursions)
    }

# Example temperature monitoring data
temp_data = pd.DataFrame({
    'timestamp': pd.date_range('2024-01-01', periods=20, freq='6H'),
    'location': ['Pharmacy-Fridge', 'Pharmacy-Fridge'] * 10,
    'temperature_f': [38, 40, 39, 41, 45, 39, 38, 40, 39, 42, 38, 39, 40, 41, 39, 38, 40, 39, 38, 40],
    'required_range': [(36, 46)] * 20
})

temp_compliance = temperature_monitoring_compliance(temp_data)
print("Temperature Compliance Summary:")
print(temp_compliance['compliance_summary'])
if temp_compliance['total_excursions'] > 0:
    print(f"\n⚠ {temp_compliance['total_excursions']} temperature excursions detected")
    print(temp_compliance['excursions'][['timestamp', 'location', 'temperature_f']])

降本策略

物资标准化分析
def standardization_opportunity_analysis(product_usage_df):
    """
    Identify standardization opportunities to reduce SKU count and costs

    product_usage_df: DataFrame with:
        - category: Product category (e.g., 'IV Catheters')
        - item_id: Specific product
        - manufacturer: Product manufacturer
        - annual_usage: Units used per year
        - unit_cost: Cost per unit
        - user_dept: Department using product
    """

    # Group by category to find fragmentation
    category_analysis = []

    for category in product_usage_df['category'].unique():
        cat_data = product_usage_df[product_usage_df['category'] == category]

        num_skus = cat_data['item_id'].nunique()
        num_manufacturers = cat_data['manufacturer'].nunique()
        total_spend = (cat_data['annual_usage'] * cat_data['unit_cost']).sum()

        # Identify top 2 products by usage
        top_products = cat_data.groupby('item_id').agg({
            'annual_usage': 'sum',
            'unit_cost': 'mean'
        }).sort_values('annual_usage', ascending=False).head(2)

        # Calculate potential savings from standardization
        # Assume standardizing to top product with 10% volume discount
        top_product_cost = top_products['unit_cost'].iloc[0]
        standardized_cost = top_product_cost * 0.90  # 10% discount
        current_spend = total_spend
        standardized_spend = cat_data['annual_usage'].sum() * standardized_cost
        potential_savings = current_spend - standardized_spend

        category_analysis.append({
            'category': category,
            'num_skus': num_skus,
            'num_manufacturers': num_manufacturers,
            'total_annual_spend': round(total_spend, 2),
            'potential_savings': round(potential_savings, 2),
            'savings_pct': round((potential_savings / current_spend * 100), 1),
            'recommendation': f'Standardize to top 1-2 products' if num_skus > 3 else 'Appropriate'
        })

    category_df = pd.DataFrame(category_analysis)
    category_df = category_df.sort_values('potential_savings', ascending=False)

    return category_df

# Example product usage data
product_usage = pd.DataFrame({
    'category': ['IV Catheter'] * 6 + ['Surgical Gloves'] * 4,
    'item_id': ['IV-Cat-A', 'IV-Cat-B', 'IV-Cat-C', 'IV-Cat-D', 'IV-Cat-E', 'IV-Cat-F',
                'Glove-X', 'Glove-Y', 'Glove-Z', 'Glove-W'],
    'manufacturer': ['MfgA', 'MfgB', 'MfgA', 'MfgC', 'MfgB', 'MfgD',
                     'MfgX', 'MfgY', 'MfgX', 'MfgZ'],
    'annual_usage': [15000, 8000, 5000, 3000, 2000, 1000,
                     50000, 30000, 15000, 5000],
    'unit_cost': [8.50, 8.75, 8.25, 9.00, 8.50, 8.80,
                  0.35, 0.38, 0.34, 0.40],
    'user_dept': ['ER', 'ICU', 'Med-Surg', 'OR', 'Oncology', 'Peds',
                  'OR', 'ER', 'ICU', 'Clinic']
})

standardization_opps = standardization_opportunity_analysis(product_usage)
print("Standardization Opportunities:")
print(standardization_opps)
print(f"\nTotal potential savings: ${standardization_opps['potential_savings'].sum():,.2f}")

工具与库

软件系统

物料管理系统:

  • GHX:医疗保健供应链管理
  • Infor CloudSuite Healthcare:物料管理与供应链
  • Oracle Healthcare:供应链与库存管理
  • SAP for Healthcare:物料管理模块
  • Jump Technologies:医疗保健供应链平台
  • Tecsys Elite:医疗保健配送与物流

库存优化:

  • OptiFlex:PAR 水平优化
  • R2D2 (Resilience):医疗保健库存分析
  • LeanTaaS:手术室与供应链优化
  • Bluesight:药品库存管理

ERP 集成:

  • Epic Supply Chain:与 Epic EHR 集成
  • Cerner:供应链模块
  • Meditech:物料管理
Python 库

数据分析:

  • pandas:数据处理与分析
  • numpy:数值计算
  • scipy:统计函数
  • statsmodels:时间序列与统计

优化:

  • pulp:线性规划
  • scipy.optimize:优化算法
  • pyomo:优化建模

可视化:

  • matplotlib:绘图与图表
  • seaborn:统计可视化
  • plotly:交互式仪表板
  • dash:基于 Web 的仪表板

常见挑战与解决方案

挑战:PAR 水平过高与库存过剩

问题:

  • 部门囤积物资
  • 抱着“以防万一”的心态
  • 无法清晰了解实际用量

解决方案:

  • 根据用量优化 PAR
  • 定期审查 PAR 水平(每季度)
  • 培训员工了解库存的真实成本
  • 自动补货,减少囤积
  • 确保物资易于获取,减少顾虑
  • 按部门制定指标并落实问责
挑战:手术室物资浪费

问题:

  • 病例推车备货过多
  • 医生偏好各异
  • 已拆封但未使用的物品

解决方案:

  • 按外科医生和手术跟踪使用情况
  • 标准化偏好卡
  • 加强临床参与和培训
  • 建立闭环供应链(将未使用物品退回库存)
  • 实时跟踪已拆封物品
  • 由价值分析委员会审查
挑战:产品过期与浪费

问题:

  • 周转缓慢的区域存放着临近有效期的产品
  • 周转管理不善(未遵循 FEFO)
  • 临期产品库存过多

解决方案:

  • 使用自动化有效期跟踪系统
  • 在拣货和入库时强制执行 FEFO
  • 将周转缓慢的库存调拨至使用量较大的区域
  • 与供应商签订退货协议
  • 降低周转缓慢物品的 PAR 水平
  • 在过期前 60-90 天发送实时提醒
挑战:缺货与物资供应

问题:

  • 需求意外激增
  • 供应商交付问题
  • 库存数量不准确

解决方案:

  • 根据需求波动设置安全库存
  • 对关键物品实行双重采购
  • 供应商管理库存(VMI)
  • 实时查看库存情况
  • 在达到再订货点时自动提醒
  • 实施循环盘点计划
  • 签订应急物资供应协议
挑战:合规与监管风险

问题:

  • 温度超标
  • 使用区域内存在过期产品
  • 受管制物质追踪
  • 召回管理

解决方案:

  • 自动温度监控并发送警报
  • 要求每日检查有效期
  • 自动追踪受管制物质
  • 追踪批次,为召回做好准备
  • 定期合规审计
  • 员工培训与能力评估
  • 电子追踪系统
挑战:员工抵触变革

问题:

  • “我们一直都是这么做的”
  • 担心缺货
  • 担心增加工作量

解决方案:

  • 尽早争取临床领导层参与
  • 开展试点项目,展示成效
  • 用数据回应疑虑
  • 简化流程(减轻工作,而非增加工作)
  • 取得快速成效,建立信任
  • 培训与支持
  • 庆祝成功

输出格式

医院物流优化报告

执行摘要:

  • 当前各类别的库存投资
  • 关键绩效指标(周转率、服务水平、浪费)
  • 建议的改进措施及财务影响
  • 实施时间表

PAR 水平优化结果:

部门项目当前 PAR建议 PAR变动年度节省
OR-1IV 导管 22g10075-25$2,500
ICU缝线 3-05065+15($180)
ER纱布 4x4500400-100$60

库存分类:

类别项目数量ABC-VEN 矩阵目标服务水平管理策略
心脏植入物45AV99.9%寄售,严格管控
IV 用品120BV/BE98%每周审核,标准 PAR
一般用品850CE/CN95%每月审核,自动化

减少浪费的机会:

机会当前年度浪费改进措施潜在节省
OR 手术案例车超额拣货$125,000统一偏好卡$87,500
产品过期$68,000先进先出 + 有效期监控$51,000
PAR 水平过高$45,000调整 PAR 至合理水平$45,000
SKU 数量膨胀$92,000标准化计划$64,400
总计$330,000$247,900

实施路线图:

阶段 1(第 1-3 个月):奠定基础

  • 为排名前 20% 的项目(A 类项目)实施 PAR 优化
  • 部署有效期追踪系统
  • 开始追踪 OR 利用率

阶段 2(第 4-6 个月):扩大实施

  • 将优化后的 PAR 推广至所有地点
  • 启动标准化计划
  • 实施自动补货

阶段 3(第 7-12 个月):优化

  • 基于数据持续改进
  • 扩展至所有产品类别
  • 开展高级分析与预测

关键绩效指标:

指标基线目标时间表
库存周转率8.512.012 个月
缺货率2.3%0.5%6 个月
过期浪费$68K/年$17K/年9 个月
OR 利用率67%85%12 个月
库存持有天数433012 个月

需要提出的问题

如果需要更多背景信息:

  1. 这是什么类型的医疗机构?(急性护理、专科、门诊)
  2. 有多少张床位?年患者量是多少?
  3. 目前使用什么物料管理系统?
  4. 当前库存投资和周转率是多少?
  5. 最大的痛点是什么?(缺货、过期、成本、合规)
  6. 使用哪种配送模式?(申领、交换车、手术案例车)
  7. 是否有需要重点关注的特定部门或产品类别?
  8. 必须满足哪些合规或监管要求?
  9. 实施时间表和预算是什么?
  10. 谁是关键利益相关者?(物料管理、护理、医生、财务)

相关技能

  • pharmacy-supply-chain:药品供应链专属运营
  • medical-device-distribution:医疗器械物流与配送
  • clinical-trial-logistics:临床试验供应管理
  • value-analysis:医疗保健价值分析与产品评估
  • inventory-optimization:通用库存优化技术
  • demand-forecasting:适用于医疗保健的预测方法
  • compliance-management:法规合规与质量管理
  • warehouse-slotting-optimization:优化存储位置
  • track-and-trace:产品追溯与序列化

评论

支持 Markdown