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. 2ビン(かんばん)システム

  • 視覚的な補充システムです
  • 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:ウェブベースのダッシュボード

よくある課題と解決策

課題: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,000FEFO + 期限監視$51,000
過剰な PAR レベル$45,000PAR の適正化$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:製品のトレーサビリティとシリアル化

コメント