Skip to content

Explore Prediction Bundles

This notebook loads pickled hierarchical predictors and allows you to explore the prediction bundles that were created.

import pickle
from pathlib import Path
import pandas as pd
import numpy as np

from patientflow.predict.hierarchy import (
    HierarchicalPredictor,
    FlowSelection,
    create_hierarchical_predictor,
)
from patientflow.predict.subspecialty import SubspecialtyPredictionInputs

Load Pickled Data

Update the path below to point to your pickle file.

# Update this path to your pickle file
pickle_path = "/Users/zellaking/Downloads/hierarchical_predictors_with_inputs.pkl"

with open(pickle_path, 'rb') as f:
    pickled_data = pickle.load(f)

print(f"Loaded pickle file: {pickle_path}")
print(f"Keys in pickle: {list(pickled_data.keys())}")

Loaded pickle file: /Users/zellaking/Downloads/hierarchical_predictors_with_inputs.pkl
Keys in pickle: ['all', 'elective', 'emergency']


/var/folders/lr/pm79dxzs0v70y4gz98dl13440000gn/T/ipykernel_12431/3782410358.py:5: DeprecationWarning: numpy.core.numeric is deprecated and has been renamed to numpy._core.numeric. The numpy._core namespace contains private NumPy internals and its use is discouraged, as NumPy internals can change without warning in any release. In practice, most real-world usage of numpy.core is to access functionality in the public NumPy API. If that is the case, use the public NumPy API. If not, you are using NumPy internals. If you would still like to access an internal attribute, use numpy._core.numeric._frombuffer.
  pickled_data = pickle.load(f)

Extract Data from 'all' Entry

if 'all' not in pickled_data:
    raise ValueError(f"Pickled data must contain 'all' key. Available keys: {list(pickled_data.keys())}")

all_data = pickled_data['all']

# Extract all components
predictor = all_data['predictor']
subspecialty_data = all_data['subspecialty_data']
hierarchy_df = all_data['hierarchy_df']
column_mapping = all_data['column_mapping']
top_level_id = all_data['top_level_id']

print(f"Number of subspecialties: {len(subspecialty_data)}")
print(f"Subspecialty IDs: {list(subspecialty_data.keys())[:10]}..." if len(subspecialty_data) > 10 else f"Subspecialty IDs: {list(subspecialty_data.keys())}")
print(f"Top level ID: {top_level_id}")
print(f"\nHierarchy DataFrame shape: {hierarchy_df.shape}")
print(f"Column mapping: {column_mapping}")

Number of subspecialties: 291
Subspecialty IDs: ['Oncology - Gynae - Clinical', 'Neurology - Stroke ASU', 'Neurosurgery - Epilepsy', 'Haematology - BMT - Treatment', 'CYPPS - Psychiatry', 'Neurosurgery - Stereotactic Functional', 'Neurosurgery - Gamma Knife', 'Paediatric - Respiratory Medicine', 'Infectious Diseases - COVID-19 Medicines Delivery', 'Neurosurgery - Vascular']...
Top level ID: uclh

Hierarchy DataFrame shape: (290, 4)
Column mapping: {'sub_specialty': 'subspecialty', 'reporting_unit': 'reporting_unit', 'division': 'division', 'board': 'board'}

Inspect Hierarchy DataFrame

hierarchy_df.head(10)

sub_specialty reporting_unit division board
0 Neurology - Upper Limb Rehab & Stroke Queen Square Specialist Hospitals Board
2 Oncology - Head and Neck - Clinical Oncology Cancer Services Cancer & Surgery Board
3 Oncology - Radiotherapy Oncology Cancer Services Cancer & Surgery Board
4 Neurology - Spasticity Rehab & Stroke Queen Square Specialist Hospitals Board
5 Neurology - Inherited Metabolic Diseases Specialist Services Queen Square Specialist Hospitals Board
6 Haematology - ITP Haematology Cancer Services Cancer & Surgery Board
7 ENT - Ears Adult ENT RNENT & EDH Specialist Hospitals Board
8 Paediatric - Proton Beam Therapy Children & Young Peoples Cancer Children and Young People Specialist Hospitals Board
9 Paediatric - Surgery Paediatric Surgery Children and Young People Specialist Hospitals Board
11 Orthopaedics - Upper Limb Trauma & Orthopaedics Surgical Specialties Cancer & Surgery Board
print(f"Hierarchy levels:")
for col in hierarchy_df.columns:
    unique_count = hierarchy_df[col].nunique()
    print(f"  {col}: {unique_count} unique values")

Hierarchy levels:
  sub_specialty: 290 unique values
  reporting_unit: 55 unique values
  division: 14 unique values
  board: 3 unique values

Inspect Subspecialty Prediction Inputs

# Look at one subspecialty's inputs
first_spec_id = list(subspecialty_data.keys())[0]
first_spec_inputs = subspecialty_data[first_spec_id]

print(f"Example subspecialty: {first_spec_id}")
print(f"\n{first_spec_inputs}")

Example subspecialty: Oncology - Gynae - Clinical

SubspecialtyPredictionInputs(subspecialty='Oncology - Gynae - Clinical')
  INFLOWS:
    Admissions from current ED               PMF[0:10]: [0.970, 0.030, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.0 of 27 patients in ED)
    ED yet-to-arrive admissions              λ = 0.014
    Non-ED emergency admissions              λ = 0.000
    Elective admissions                      λ = 0.075
    Elective transfers from other subspecialties PMF[0:2]: [1.000, 0.000] (E=0.0)
    Emergency transfers from other subspecialties PMF[0:7]: [0.957, 0.042, 0.001, 0.000, 0.000, 0.000, 0.000] (E=0.0)
  OUTFLOWS:
    Emergency inpatient departures           PMF[0:2]: [0.905, 0.095] (E=0.1 of 1 emergency patients in subspec)
    Elective inpatient departures            PMF[0:2]: [1.000, 0.000] (E=0.0 of 1 elective patients in subspec)
# Inspect inflows for the first subspecialty
print(f"Inflows for {first_spec_id}:")
for flow_id, flow_input in first_spec_inputs.inflows.items():
    if flow_input.flow_type == 'pmf':
        pmf = flow_input.distribution
        print(f"  {flow_id}: PMF with {len(pmf)} elements, sum={pmf.sum():.4f}, mean={np.sum(pmf * np.arange(len(pmf))):.4f}")
    elif flow_input.flow_type == 'poisson':
        lam = flow_input.distribution
        print(f"  {flow_id}: Poisson(λ={lam:.4f})")

Inflows for Oncology - Gynae - Clinical:
  ed_current: PMF with 28 elements, sum=1.0000, mean=0.0303
  ed_yta: Poisson(λ=0.0141)
  non_ed_yta: Poisson(λ=0.0000)
  elective_yta: Poisson(λ=0.0755)
  elective_transfers: PMF with 2 elements, sum=1.0000, mean=0.0000
  emergency_transfers: PMF with 7 elements, sum=1.0000, mean=0.0436
# Inspect outflows for the first subspecialty
print(f"Outflows for {first_spec_id}:")
for flow_id, flow_input in first_spec_inputs.outflows.items():
    if flow_input.flow_type == 'pmf':
        pmf = flow_input.distribution
        print(f"  {flow_id}: PMF with {len(pmf)} elements, sum={pmf.sum():.4f}, mean={np.sum(pmf * np.arange(len(pmf))):.4f}")
    elif flow_input.flow_type == 'poisson':
        lam = flow_input.distribution
        print(f"  {flow_id}: Poisson(λ={lam:.4f})")

Outflows for Oncology - Gynae - Clinical:
  emergency_departures: PMF with 2 elements, sum=1.0000, mean=0.0953
  elective_departures: PMF with 2 elements, sum=1.0000, mean=0.0000

Inspect Existing Prediction Bundles (from Pickled Predictor)

# Get flow selection from the pickled predictor
if predictor.cache:
    first_bundle = next(iter(predictor.cache.values()))
    flow_selection = first_bundle.flow_selection
    print(f"Flow selection from pickled predictor: {flow_selection.cohort}")
    print(f"\nCached bundles in predictor: {len(predictor.cache)} entities")
    print(f"\nEntity IDs in cache:")
    for entity_id in list(predictor.cache.keys())[:20]:
        print(f"  {entity_id}")
    if len(predictor.cache) > 20:
        print(f"  ... and {len(predictor.cache) - 20} more")

Flow selection from pickled predictor: all

Cached bundles in predictor: 364 entities

Entity IDs in cache:
  subspecialty:Oncology - Gynae - Clinical
  subspecialty:Neurology - Stroke ASU
  subspecialty:Neurosurgery - Epilepsy
  subspecialty:Haematology - BMT - Treatment
  subspecialty:CYPPS - Psychiatry
  subspecialty:Neurosurgery - Stereotactic Functional
  subspecialty:Neurosurgery - Gamma Knife
  subspecialty:Paediatric - Respiratory Medicine
  subspecialty:Infectious Diseases - COVID-19 Medicines Delivery
  subspecialty:Neurosurgery - Vascular
  subspecialty:Gastroenterology - Adolescent
  subspecialty:Gynaecology - Endometriosis
  subspecialty:Neurology - Neuro Ophthalmology
  subspecialty:Respiratory - Lung Cancer
  subspecialty:Obstetrics - Still Birth
  subspecialty:Neurology - Autonomics
  subspecialty:Urology - Stones
  subspecialty:Neurosurgery - General Cranial
  subspecialty:Gynaecology - Urogynaecology
  subspecialty:Oncology - Interventional Oncology Service
  ... and 344 more
# Inspect a specific prediction bundle from the cache
# You can modify this to look at different entities
entity_to_inspect = list(predictor.cache.keys())[0] if predictor.cache else None

if entity_to_inspect:
    bundle = predictor.cache[entity_to_inspect]
    print(f"\nPrediction Bundle for: {entity_to_inspect}")
    print(f"Entity ID: {bundle.entity_id}")
    print(f"Entity Type: {bundle.entity_type}")
    print(f"\n{bundle}")

Prediction Bundle for: subspecialty:Oncology - Gynae - Clinical
Entity ID: Oncology - Gynae - Clinical
Entity Type: subspecialty

PredictionBundle(subspecialty: Oncology - Gynae - Clinical)
  Arrivals:    PMF[0:10]: [0.849, 0.140, 0.011, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.2)
  Departures:  PMF[0:3]: [0.905, 0.095, 0.000] (E=0.1)
  Net flow:    PMF[-2:6]: [0.000, 0.081, 0.781, 0.127, 0.010, 0.001, 0.000, 0.000] (E=0.1)
  Flows:       selection cohort=all inflows(ed_current=True, ed_yta=True, non_ed_yta=True, elective_yta=True, transfers_in=True) outflows(departures=True)

Create New Predictions with Different k_sigma Values

You can create new predictions with different k_sigma values to see how they affect the results.

# Choose a k_sigma value to test
k_sigma = 8.0

# Create a new predictor with this k_sigma
new_predictor = create_hierarchical_predictor(
    hierarchy_df,
    column_mapping,
    top_level_id,
    k_sigma=k_sigma,
)

print(f"Created new predictor with k_sigma={k_sigma}")

Created new predictor with k_sigma=8.0
# Generate predictions with the new k_sigma
# Use the flow_selection from the pickled predictor
new_results = new_predictor.predict_all_levels(
    subspecialty_data,
    flow_selection=flow_selection
)

print(f"Generated {len(new_results)} prediction bundles")
print(f"\nEntity IDs in results:")
for entity_id in list(new_results.keys())[:20]:
    print(f"  {entity_id}")
if len(new_results) > 20:
    print(f"  ... and {len(new_results) - 20} more")

Generated 364 prediction bundles

Entity IDs in results:
  subspecialty:Oncology - Gynae - Clinical
  subspecialty:Neurology - Stroke ASU
  subspecialty:Neurosurgery - Epilepsy
  subspecialty:Haematology - BMT - Treatment
  subspecialty:CYPPS - Psychiatry
  subspecialty:Neurosurgery - Stereotactic Functional
  subspecialty:Neurosurgery - Gamma Knife
  subspecialty:Paediatric - Respiratory Medicine
  subspecialty:Infectious Diseases - COVID-19 Medicines Delivery
  subspecialty:Neurosurgery - Vascular
  subspecialty:Gastroenterology - Adolescent
  subspecialty:Gynaecology - Endometriosis
  subspecialty:Neurology - Neuro Ophthalmology
  subspecialty:Respiratory - Lung Cancer
  subspecialty:Obstetrics - Still Birth
  subspecialty:Neurology - Autonomics
  subspecialty:Urology - Stones
  subspecialty:Neurosurgery - General Cranial
  subspecialty:Gynaecology - Urogynaecology
  subspecialty:Oncology - Interventional Oncology Service
  ... and 344 more
# Inspect a specific prediction bundle from the new results
# Try different entity IDs to explore
entity_to_inspect = list(new_results.keys())[0]

bundle = new_results[entity_to_inspect]
print(f"\nPrediction Bundle for: {entity_to_inspect}")
print(f"\n{bundle}")

Prediction Bundle for: subspecialty:Oncology - Gynae - Clinical

PredictionBundle(subspecialty: Oncology - Gynae - Clinical)
  Arrivals:    PMF[0:10]: [0.849, 0.140, 0.011, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.2)
  Departures:  PMF[0:3]: [0.905, 0.095, 0.000] (E=0.1)
  Net flow:    PMF[-2:8]: [0.000, 0.081, 0.781, 0.128, 0.010, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.1)
  Flows:       selection cohort=all inflows(ed_current=True, ed_yta=True, non_ed_yta=True, elective_yta=True, transfers_in=True) outflows(departures=True)
# Compare PMF sizes for top-level entity
top_level_key = f"hospital:{top_level_id}" if f"hospital:{top_level_id}" in new_results else None
if not top_level_key:
    # Try to find top level by checking entity types
    for key, bundle in new_results.items():
        if bundle.entity_type == 'hospital':
            top_level_key = key
            break

if top_level_key:
    bundle = new_results[top_level_key]
    print(f"Top-level bundle: {top_level_key}")
    print(f"\nArrivals PMF length: {len(bundle.arrivals.probabilities)}")
    print(f"Arrivals expected value: {bundle.arrivals.expected_value:.2f}")
    print(f"\nDepartures PMF length: {len(bundle.departures.probabilities)}")
    print(f"Departures expected value: {bundle.departures.expected_value:.2f}")
    print(f"\nNet flow PMF length: {len(bundle.net_flow.probabilities)}")
    print(f"Net flow expected value: {bundle.net_flow.expected_value:.2f}")
    print(f"Net flow offset: {bundle.net_flow.offset}")

    # Show percentiles
    print(f"\nArrivals percentiles: {bundle.arrivals.percentiles}")
    print(f"Departures percentiles: {bundle.departures.percentiles}")
    print(f"Net flow percentiles: {bundle.net_flow.percentiles}")
else:
    print("Could not find top-level entity")

Top-level bundle: hospital:uclh

Arrivals PMF length: 189
Arrivals expected value: 106.17

Departures PMF length: 144
Departures expected value: 86.42

Net flow PMF length: 332
Net flow expected value: 19.75
Net flow offset: -143

Arrivals percentiles: {50: 106, 75: 113, 90: 119, 95: 123, 99: 130}
Departures percentiles: {50: 86, 75: 91, 90: 95, 95: 98, 99: 103}
Net flow percentiles: {50: 20, 75: 28, 90: 36, 95: 40, 99: 49}

Compare Different k_sigma Values

Compare how different k_sigma values affect PMF sizes and predictions.

# Test multiple k_sigma values
k_sigma_values = [4.0, 6.0, 8.0, 10.0, 12.0]
comparison_results = {}

for k_sigma in k_sigma_values:
    predictor = create_hierarchical_predictor(
        hierarchy_df,
        column_mapping,
        top_level_id,
        k_sigma=k_sigma,
    )

    results = predictor.predict_all_levels(
        subspecialty_data,
        flow_selection=flow_selection
    )

    # Find top-level bundle
    top_bundle = None
    for key, bundle in results.items():
        if bundle.entity_type == 'hospital':
            top_bundle = bundle
            break

    if top_bundle:
        comparison_results[k_sigma] = {
            'arrivals_pmf_len': len(top_bundle.arrivals.probabilities),
            'departures_pmf_len': len(top_bundle.departures.probabilities),
            'net_flow_pmf_len': len(top_bundle.net_flow.probabilities),
            'arrivals_expected': top_bundle.arrivals.expected_value,
            'departures_expected': top_bundle.departures.expected_value,
            'net_flow_expected': top_bundle.net_flow.expected_value,
        }

# Display comparison
print(f"{'k_sigma':<10} {'Arr PMF':<12} {'Dep PMF':<12} {'Net PMF':<12} {'Arr E[X]':<12} {'Dep E[X]':<12} {'Net E[X]':<12}")
print("-" * 80)
for k_sigma in sorted(comparison_results.keys()):
    r = comparison_results[k_sigma]
    print(f"{k_sigma:<10.1f} {r['arrivals_pmf_len']:<12} {r['departures_pmf_len']:<12} {r['net_flow_pmf_len']:<12} "
          f"{r['arrivals_expected']:<12.2f} {r['departures_expected']:<12.2f} {r['net_flow_expected']:<12.2f}")

k_sigma    Arr PMF      Dep PMF      Net PMF      Arr E[X]     Dep E[X]     Net E[X]    
--------------------------------------------------------------------------------
4.0        148          116          263          106.09       86.42        19.67       
6.0        169          130          298          106.16       86.42        19.74       
8.0        189          144          332          106.17       86.42        19.75       
10.0       209          158          366          106.17       86.42        19.75       
12.0       230          172          401          106.17       86.42        19.75

Explore Specific Subspecialty Predictions

Verify Net Flow Bounds Based on k_sigma

Check that net flow bounds are correct: net flow should range from [-max_departures, +max_arrivals] where the max values come from the capped arrivals and departures distributions.

def verify_net_flow_bounds(bundle):
    """Verify that net flow bounds are correct based on arrivals and departures.

    Net flow should have:
    - Offset = -(max_departures + departures.offset)
    - Support range: [-(max_departures + dep_offset), +(max_arrivals + arr_offset)]
    - PMF length = (max_arrivals + arr_offset) + (max_departures + dep_offset) + 1

    Where:
    - max_arrivals = len(arrivals.probabilities) - 1
    - max_departures = len(departures.probabilities) - 1
    - arr_offset = arrivals.offset (typically 0)
    - dep_offset = departures.offset (typically 0)

    Returns a dictionary with verification results.
    """
    arrivals = bundle.arrivals
    departures = bundle.departures
    net_flow = bundle.net_flow

    # Calculate max values (accounting for offsets)
    arr_offset = arrivals.offset
    dep_offset = departures.offset
    max_arrivals_idx = len(arrivals.probabilities) - 1
    max_departures_idx = len(departures.probabilities) - 1

    # Maximum actual values (not indices)
    max_arrivals_value = arr_offset + max_arrivals_idx
    max_departures_value = dep_offset + max_departures_idx

    # Expected net flow bounds
    # Net flow = arrivals - departures
    # Minimum: min_arrivals - max_departures = arr_offset - max_departures_value
    # Maximum: max_arrivals - min_departures = max_arrivals_value - dep_offset
    expected_min_value = arr_offset - max_departures_value
    expected_max_value = max_arrivals_value - dep_offset
    expected_offset = expected_min_value
    expected_pmf_length = expected_max_value - expected_min_value + 1

    # Actual values
    actual_offset = net_flow.offset
    actual_min_value = net_flow.offset
    actual_max_value = net_flow.offset + len(net_flow.probabilities) - 1
    actual_pmf_length = len(net_flow.probabilities)

    # Verify
    offset_correct = actual_offset == expected_offset
    min_value_correct = actual_min_value == expected_min_value
    max_value_correct = actual_max_value == expected_max_value
    pmf_length_correct = actual_pmf_length == expected_pmf_length

    all_correct = offset_correct and min_value_correct and max_value_correct and pmf_length_correct

    return {
        'entity_id': bundle.entity_id,
        'entity_type': bundle.entity_type,
        'arr_offset': arr_offset,
        'dep_offset': dep_offset,
        'max_arrivals_idx': max_arrivals_idx,
        'max_departures_idx': max_departures_idx,
        'max_arrivals_value': max_arrivals_value,
        'max_departures_value': max_departures_value,
        'expected_offset': expected_offset,
        'actual_offset': actual_offset,
        'offset_correct': offset_correct,
        'expected_min_value': expected_min_value,
        'actual_min_value': actual_min_value,
        'min_value_correct': min_value_correct,
        'expected_max_value': expected_max_value,
        'actual_max_value': actual_max_value,
        'max_value_correct': max_value_correct,
        'expected_pmf_length': expected_pmf_length,
        'actual_pmf_length': actual_pmf_length,
        'pmf_length_correct': pmf_length_correct,
        'all_correct': all_correct,
    }

# Verify bounds for a specific bundle
if 'new_results' in locals() and new_results:
    bundle_to_check = list(new_results.values())[0]
    verification = verify_net_flow_bounds(bundle_to_check)

    print(f"Verification for: {verification['entity_id']} ({verification['entity_type']})")
    print(f"\nArrivals:")
    print(f"  Offset: {verification['arr_offset']}, Max index: {verification['max_arrivals_idx']}, Max value: {verification['max_arrivals_value']}")
    print(f"\nDepartures:")
    print(f"  Offset: {verification['dep_offset']}, Max index: {verification['max_departures_idx']}, Max value: {verification['max_departures_value']}")
    print(f"\nNet Flow Bounds:")
    print(f"  Expected offset: {verification['expected_offset']}, Actual: {verification['actual_offset']} {'✓' if verification['offset_correct'] else '✗'}")
    print(f"  Expected min value: {verification['expected_min_value']}, Actual: {verification['actual_min_value']} {'✓' if verification['min_value_correct'] else '✗'}")
    print(f"  Expected max value: {verification['expected_max_value']}, Actual: {verification['actual_max_value']} {'✓' if verification['max_value_correct'] else '✗'}")
    print(f"  Expected PMF length: {verification['expected_pmf_length']}, Actual: {verification['actual_pmf_length']} {'✓' if verification['pmf_length_correct'] else '✗'}")
    print(f"\n{'All checks passed!' if verification['all_correct'] else 'Some checks failed!'}")
else:
    print("Run the cells above to generate predictions first.")

Verification for: Oncology - Gynae - Clinical (subspecialty)

Arrivals:
  Offset: 0, Max index: 38, Max value: 38

Departures:
  Offset: 0, Max index: 2, Max value: 2

Net Flow Bounds:
  Expected offset: -2, Actual: -2 ✓
  Expected min value: -2, Actual: -2 ✓
  Expected max value: 38, Actual: 38 ✓
  Expected PMF length: 41, Actual: 41 ✓

All checks passed!
# Verify bounds across all entities and different k_sigma values
k_sigma_test_values = [4.0, 6.0, 8.0, 10.0, 12.0, 50.0, 100.0]
verification_results = {}

for k_sigma in k_sigma_test_values:
    predictor = create_hierarchical_predictor(
        hierarchy_df,
        column_mapping,
        top_level_id,
        k_sigma=k_sigma,
    )

    results = predictor.predict_all_levels(
        subspecialty_data,
        flow_selection=flow_selection
    )

    # Verify all bundles
    entity_verifications = {}
    for entity_id, bundle in results.items():
        verification = verify_net_flow_bounds(bundle)
        entity_verifications[entity_id] = verification

    verification_results[k_sigma] = entity_verifications

# Summary: Check how many entities pass verification for each k_sigma
print("Verification Summary:")
print(f"{'k_sigma':<10} {'Total Entities':<15} {'All Correct':<15} {'Failed':<15}")
print("-" * 60)

for k_sigma in sorted(verification_results.keys()):
    verifications = verification_results[k_sigma]
    total = len(verifications)
    all_correct = sum(1 for v in verifications.values() if v['all_correct'])
    failed = total - all_correct
    print(f"{k_sigma:<10.1f} {total:<15} {all_correct:<15} {failed:<15}")

# Show any failures
print("\n" + "="*60)
print("Detailed Failures (if any):")
print("="*60)

for k_sigma in sorted(verification_results.keys()):
    verifications = verification_results[k_sigma]
    failures = [(eid, v) for eid, v in verifications.items() if not v['all_correct']]
    if failures:
        print(f"\nk_sigma = {k_sigma}:")
        for entity_id, v in failures:
            print(f"  {entity_id}:")
            if not v['offset_correct']:
                print(f"    Offset: expected {v['expected_offset']}, got {v['actual_offset']}")
            if not v['min_value_correct']:
                print(f"    Min value: expected {v['expected_min_value']}, got {v['actual_min_value']}")
            if not v['max_value_correct']:
                print(f"    Max value: expected {v['expected_max_value']}, got {v['actual_max_value']}")
            if not v['pmf_length_correct']:
                print(f"    PMF length: expected {v['expected_pmf_length']}, got {v['actual_pmf_length']}")
    else:
        print(f"\nk_sigma = {k_sigma}: All entities passed verification ✓")

Verification Summary:
k_sigma    Total Entities  All Correct     Failed         
------------------------------------------------------------
4.0        364             364             0              
6.0        364             364             0              
8.0        364             364             0              
10.0       364             364             0              
12.0       364             364             0              
50.0       364             364             0              
100.0      364             364             0

============================================================
Detailed Failures (if any):
============================================================

k_sigma = 4.0: All entities passed verification ✓

k_sigma = 6.0: All entities passed verification ✓

k_sigma = 8.0: All entities passed verification ✓

k_sigma = 10.0: All entities passed verification ✓

k_sigma = 12.0: All entities passed verification ✓

k_sigma = 50.0: All entities passed verification ✓

k_sigma = 100.0: All entities passed verification ✓
# Show how k_sigma affects net flow bounds for top-level entity
print("How k_sigma affects net flow bounds (top-level entity):")
print(f"{'k_sigma':<10} {'Max Arr Val':<12} {'Max Dep Val':<12} {'Net Min':<10} {'Net Max':<10} {'Net PMF Len':<12} {'Status':<10}")
print("-" * 80)

for k_sigma in sorted(verification_results.keys()):
    verifications = verification_results[k_sigma]

    # Find top-level entity
    top_verification = None
    for entity_id, v in verifications.items():
        if v['entity_type'] == 'hospital':
            top_verification = v
            break

    if top_verification:
        v = top_verification
        status = "✓" if v['all_correct'] else "✗"
        print(f"{k_sigma:<10.1f} {v['max_arrivals_value']:<12} {v['max_departures_value']:<12} "
              f"{v['actual_min_value']:<10} {v['actual_max_value']:<10} {v['actual_pmf_length']:<12} {status:<10}")

How k_sigma affects net flow bounds (top-level entity):
k_sigma    Max Arr Val  Max Dep Val  Net Min    Net Max    Net PMF Len  Status    
--------------------------------------------------------------------------------
4.0        147          115          -115       147        263          ✓         
6.0        168          129          -129       168        298          ✓         
8.0        188          143          -143       188        332          ✓         
10.0       208          157          -157       208        366          ✓         
12.0       229          171          -171       229        401          ✓         
50.0       614          436          -436       614        1051         ✓         
100.0      1122         563          -563       1122       1686         ✓
# Choose a subspecialty to inspect in detail
spec_to_inspect = list(subspecialty_data.keys())[0]
spec_key = f"subspecialty:{spec_to_inspect}"

if spec_key in new_results:
    spec_bundle = new_results[spec_key]
    print(f"Detailed view of subspecialty: {spec_to_inspect}")
    print(f"\n{spec_bundle}")

    # Show PMF arrays
    print(f"\n\nArrivals PMF (first 20 values):")
    print(spec_bundle.arrivals.probabilities[:20])

    print(f"\nDepartures PMF (first 20 values):")
    print(spec_bundle.departures.probabilities[:20])

    print(f"\nNet Flow PMF (first 20 values, offset={spec_bundle.net_flow.offset}):")
    print(spec_bundle.net_flow.probabilities[:20])
else:
    print(f"Could not find {spec_key} in results")

Detailed view of subspecialty: Oncology - Gynae - Clinical

PredictionBundle(subspecialty: Oncology - Gynae - Clinical)
  Arrivals:    PMF[0:10]: [0.849, 0.140, 0.011, 0.001, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.2)
  Departures:  PMF[0:3]: [0.905, 0.095, 0.000] (E=0.1)
  Net flow:    PMF[-2:8]: [0.000, 0.081, 0.781, 0.128, 0.010, 0.000, 0.000, 0.000, 0.000, 0.000] (E=0.1)
  Flows:       selection cohort=all inflows(ed_current=True, ed_yta=True, non_ed_yta=True, elective_yta=True, transfers_in=True) outflows(departures=True)


Arrivals PMF (first 20 values):
[8.48694244e-01 1.39805058e-01 1.09366768e-02 5.45197028e-04
 1.83986641e-05 4.18752076e-07 6.35445372e-09 6.36642404e-11
 4.18183760e-13 1.80973266e-15 5.31576184e-18 1.09678047e-20
 1.63744117e-23 1.81122916e-26 1.51211119e-29 9.66733306e-33
 4.78723204e-36 1.85225818e-39 5.63508145e-43 1.35349652e-46]

Departures PMF (first 20 values):
[0.90469798 0.09530202 0.        ]

Net Flow PMF (first 20 values, offset=-2):
[0.00000000e+00 8.08822721e-02 7.81135676e-01 1.27523642e-01
 9.94634782e-03 4.94992082e-04 1.66851423e-05 3.79449751e-07
 5.75492880e-09 5.76367637e-11 3.78502476e-13 1.63776810e-15
 4.81020427e-18 9.92411128e-21 1.48156234e-23 1.63875948e-26
 1.36809608e-29 8.74647296e-33 4.33117570e-36 1.67578794e-39]