Skip to main content

Platform for Agentic Clinical Intelligence with Networked Graphs

Project description

PACING: Platform for Agentic Clinical Intelligence with Networked Graphs

A clinical decision support system for Substance Use Disorder (SUD) treatment that combines real-time AI assistance with longitudinal risk analysis.

๐ŸŽฏ Overview

PACING is a scaffold-first platform designed for extensibility, auditability, and privacy. It operates in two distinct modes:

1. Live Session Mode ("The Stream")

Real-time audio transcription with parallel Agentic Sidecars that assist clinicians:

  • Guide: Monitors for missing critical information
  • Auditor: Flags low-confidence transcriptions for human review
  • Scribe: Extracts structured entities (medications, events) from conversations

2. Longitudinal Analysis Mode ("The Graph")

Patient history visualization and risk modeling:

  • Risk Assessment: Probabilistic models estimate relapse risk
  • What-If Simulation: Explore hypothetical scenarios ("What if housing stabilizes?")
  • Evidence-Based: Models explain their reasoning with contributing factors

๐Ÿ—๏ธ Architecture Philosophy

Glass Box Design

Unlike black-box AI, PACING is auditable and explainable:

  • Risk models list contributing factors with evidence
  • Low-confidence transcriptions are flagged for human review
  • All decisions are traceable and verifiable

Privacy by Design

Two operating modes enforce privacy boundaries:

  • DEV_MODE: Raw audio/transcripts persisted for debugging
  • PROD_MODE: Raw data is ephemeral; only structured extractions retained

Plugin Architecture

Strictly typed dependency injection allows swapping components:

# Inject your own transcriber
platform.set_transcriber(DeepgramTranscriber(api_key="..."))

# Inject your own risk model
platform.set_risk_model(MyCustomBayesianNetwork())

# Add custom agents
platform.register_agent(MyResearchAgent())

๐Ÿš€ Quick Start

Installation

pip install -e .

Basic Usage: Live Session Mode

import asyncio
from datetime import datetime
from pacing import (
    PacingPlatform,
    OperatingMode,
    SessionMetadata,
    MockAudioProvider,
    UncertaintyAuditor
)

# Initialize platform in DEV mode
platform = PacingPlatform(operating_mode=OperatingMode.DEV_MODE)

# Register agents
auditor = UncertaintyAuditor(confidence_threshold=0.70)
platform.register_agent(auditor)

# Create session metadata
session = SessionMetadata(
    session_id="session_001",
    patient_id="patient_123",
    clinician_id="clinician_456",
    start_time=datetime.now(),
    session_type="counseling"
)

# Start live session with mock audio
audio = MockAudioProvider(total_duration_sec=30.0)

async def run_session():
    await platform.start_live_session(session, audio)
    # Session runs until audio completes or stopped manually
    await asyncio.sleep(35)
    await platform.stop_live_session()

    # Check what was flagged
    review_queue = auditor.get_review_queue()
    print(f"Flagged {len(review_queue)} segments for review:")
    for item in review_queue:
        print(f"  - [{item.priority}] {item.transcription.text}")
        print(f"    Reason: {item.reason}")

asyncio.run(run_session())

Basic Usage: Risk Assessment

from datetime import datetime, timedelta
from pacing import (
    PacingPlatform,
    PatientGraph,
    Event,
    EventType,
    SubstanceUse,
    SubstanceType,
    SubstanceUseStatus,
    Intervention,
    InterventionType
)

# Initialize platform
platform = PacingPlatform()

# Create patient graph
patient = PatientGraph(
    patient_id="patient_123",
    events=[
        Event(
            event_id="evt_001",
            event_type=EventType.JOB_CHANGE,
            description="Lost job due to budget cuts",
            date=datetime.now() - timedelta(days=15),
            impact_score=-0.7
        )
    ],
    substance_use_records=[
        SubstanceUse(
            use_id="use_001",
            substance_type=SubstanceType.OPIOIDS,
            status=SubstanceUseStatus.REMISSION,
            date=datetime.now() - timedelta(days=180),
            notes="6 months sober"
        )
    ],
    interventions=[
        Intervention(
            intervention_id="int_001",
            intervention_type=InterventionType.MEDICATION,
            description="Buprenorphine 8mg daily",
            start_date=datetime.now() - timedelta(days=180),
            effectiveness_score=0.85
        )
    ]
)

# Calculate risk
report = platform.calculate_risk(patient)

print(f"Risk Score: {report.risk_score:.2%}")
print("\nContributing Factors:")
for factor in report.risk_factors:
    direction = "โ†‘" if factor.contribution > 0 else "โ†“"
    print(f"  {direction} {factor.factor_name}: {abs(factor.contribution):.2%}")
    for evidence in factor.evidence:
        print(f"      - {evidence}")

Basic Usage: What-If Simulation

from pacing import (
    PacingPlatform,
    create_stable_housing_mutation,
    create_employment_mutation,
    create_mat_intervention_mutation
)

# Initialize platform with simulation-capable model
platform = PacingPlatform()

# Get simulation context
sim = platform.get_simulation_context(patient)

# Scenario 1: What if housing stabilizes?
result = sim.simulate_mutation(create_stable_housing_mutation())

print(f"Current Risk: {result['baseline_risk']:.2%}")
print(f"With Stable Housing: {result['modified_risk']:.2%}")
print(f"Risk Change: {result['delta']:.2%}")
print(f"\n{result['explanation']}")

# Scenario 2: Compare multiple scenarios
scenarios = {
    "Baseline": [],
    "Stable Housing": [create_stable_housing_mutation()],
    "Housing + Employment": [
        create_stable_housing_mutation(),
        create_employment_mutation(employed=True)
    ],
    "Full Support": [
        create_stable_housing_mutation(),
        create_employment_mutation(employed=True),
        create_mat_intervention_mutation()
    ]
}

comparison = sim.compare_scenarios(scenarios)

print("\n๐Ÿ“Š Scenario Comparison:")
for scenario_name in comparison["ranked"]:
    result = comparison["scenarios"][scenario_name]
    print(f"  {scenario_name}: {result['modified_risk']:.2%}")

print(f"\nโœ… Best Scenario: {comparison['best_scenario']}")

๐Ÿ”Œ Extending PACING

Adding a Custom Transcriber

from pacing.core.transcription_interfaces import ITranscriber
from pacing.models.data_models import TranscriptionResult
import numpy as np

class DeepgramTranscriber(ITranscriber):
    def __init__(self, api_key: str):
        self.api_key = api_key
        # Initialize Deepgram client...

    async def transcribe_chunk(
        self,
        audio_chunk: np.ndarray,
        sample_rate: int,
        is_final: bool = False
    ) -> TranscriptionResult:
        # Call Deepgram API...
        # Return TranscriptionResult with text and confidence
        pass

    def supports_speaker_diarization(self) -> bool:
        return True

# Use it
platform.set_transcriber(DeepgramTranscriber(api_key="your_key"))

Adding a Custom Risk Model

from pacing.core.model_interfaces import ISimulationModel
from pacing.models.data_models import PatientGraph, RiskReport

class MyBayesianNetwork(ISimulationModel):
    def calculate_risk(
        self,
        patient_data: PatientGraph,
        options = None
    ) -> RiskReport:
        # Your probabilistic inference logic
        # (e.g., using pgmpy, PyMC, or custom implementation)
        pass

    def calculate_risk_delta(self, baseline, modified, options=None):
        # Compare baseline vs modified scenarios
        pass

# Use it
platform.set_risk_model(MyBayesianNetwork())

Adding a Custom Sidecar Agent

from pacing.core.agent_interfaces import ISidecarAgent
from pacing.models.data_models import TranscriptionResult

class GuideAgent(ISidecarAgent):
    """Monitors for missing critical information."""

    def __init__(self):
        self.required_topics = {"housing", "employment", "medications"}
        self.covered_topics = set()

    async def on_transcription_update(
        self,
        transcription: TranscriptionResult,
        context = None
    ):
        text_lower = transcription.text.lower()

        # Check if any required topic is mentioned
        for topic in self.required_topics:
            if topic in text_lower:
                self.covered_topics.add(topic)

        # Alert if topics are missing
        missing = self.required_topics - self.covered_topics
        if missing:
            print(f"[Guide] Consider asking about: {', '.join(missing)}")

    def get_agent_name(self) -> str:
        return "GuideAgent"

# Use it
platform.register_agent(GuideAgent())

๐Ÿ“ Project Structure

pacing/
โ”œโ”€โ”€ core/                         # Abstract interfaces (ABCs)
โ”‚   โ”œโ”€โ”€ audio_interfaces.py       # IAudioProvider
โ”‚   โ”œโ”€โ”€ transcription_interfaces.py  # ITranscriber
โ”‚   โ”œโ”€โ”€ agent_interfaces.py       # ISidecarAgent, IScribeAgent
โ”‚   โ”œโ”€โ”€ model_interfaces.py       # IRiskModel, ISimulationModel
โ”‚   โ””โ”€โ”€ session_interfaces.py     # ISessionStream
โ”‚
โ”œโ”€โ”€ models/                       # Pydantic data models
โ”‚   โ””โ”€โ”€ data_models.py            # TranscriptionResult, PatientGraph, RiskReport, etc.
โ”‚
โ”œโ”€โ”€ impl/                         # Concrete implementations
โ”‚   โ”œโ”€โ”€ defaults/                 # Mock implementations
โ”‚   โ”‚   โ”œโ”€โ”€ mock_audio.py         # MockAudioProvider, ScriptedAudioProvider
โ”‚   โ”‚   โ”œโ”€โ”€ mock_transcriber.py   # MockTranscriber, AdaptiveConfidenceTranscriber
โ”‚   โ”‚   โ””โ”€โ”€ mock_risk_model.py    # MockBayesianModel, MockSimulationModel
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ agents/                   # Sidecar agents
โ”‚       โ””โ”€โ”€ uncertainty_auditor.py  # UncertaintyAuditor (flags low-confidence)
โ”‚
โ”œโ”€โ”€ privacy/                      # Data handling & redaction
โ”‚   โ””โ”€โ”€ (future: retention policies, PHI filtering)
โ”‚
โ”œโ”€โ”€ simulation/                   # What-If analysis engine
โ”‚   โ””โ”€โ”€ simulation_engine.py      # SimulationContext, Mutation
โ”‚
โ””โ”€โ”€ platform.py                   # PacingPlatform (main orchestrator)

๐Ÿ”’ Privacy & Security

Operating Modes

Mode Raw Audio Transcripts Extracted Entities
DEV_MODE Persisted Persisted Persisted
PROD_MODE Ephemeral Ephemeral Persisted

Recommendations for Production

  1. Use PROD_MODE unless debugging
  2. Implement PHI filtering in the privacy module
  3. Encrypt stored entities at rest
  4. Set retention policies for review queues
  5. Audit access to patient graphs
  6. Obtain informed consent for AI assistance

๐Ÿงช Testing

Running Mock Demonstrations

The platform includes mock implementations for testing without external dependencies:

# Mock audio (generates synthetic audio)
audio = MockAudioProvider(total_duration_sec=60.0)

# Mock transcriber (returns scripted text)
transcriber = MockTranscriber(latency_ms=50)

# Mock risk model (rule-based heuristics)
model = MockBayesianModel(base_risk=0.50)

Testing the Auditor

from pacing import MockTranscriber, AdaptiveConfidenceTranscriber

# Use adaptive transcriber to generate low-confidence segments
transcriber = AdaptiveConfidenceTranscriber()

# The auditor will automatically flag segments with:
# - Confidence < 0.70
# - Medical terms (even if confidence is acceptable)

๐ŸŽ“ Research Use Cases

PACING is designed to support clinical research:

Example 1: Studying Information Gathering Patterns

# Add a custom agent to track which topics are discussed
class TopicTrackingAgent(ISidecarAgent):
    def __init__(self):
        self.topic_mentions = {}

    async def on_transcription_update(self, transcription, context):
        # Count topic mentions over time
        pass

Example 2: Testing Risk Model Variants

# Compare multiple risk models on the same patient data
models = {
    "Rule-Based": RuleBasedModel(),
    "Bayesian Network": BayesianNetworkModel(),
    "Random Forest": MLModel()
}

for name, model in models.items():
    platform.set_risk_model(model)
    report = platform.calculate_risk(patient_graph)
    print(f"{name}: {report.risk_score:.2%}")

Example 3: Evaluating Simulation Accuracy

# Compare predicted vs actual outcomes
sim = platform.get_simulation_context(patient_baseline)
prediction = sim.simulate_mutation(housing_mutation)

# Later, compare to actual outcome
actual_outcome = load_patient_data_at_followup(patient_id, months=6)
actual_report = platform.calculate_risk(actual_outcome)

accuracy = abs(prediction['modified_risk'] - actual_report.risk_score)
print(f"Prediction error: {accuracy:.2%}")

๐Ÿ› ๏ธ Development Roadmap

Planned Features

  • Guide Agent implementation
  • Scribe Agent with LLM-based entity extraction
  • PHI filtering in privacy module
  • Session persistence & replay
  • Web dashboard for review queues
  • Integration with EHR systems (FHIR)
  • Real audio providers (PyAudio, sounddevice)
  • Production transcribers (Deepgram, Whisper)
  • Real probabilistic models (pgmpy, PyMC)

Contributing

This is a research scaffold. To add features:

  1. Define interfaces in pacing/core/
  2. Implement in pacing/impl/
  3. Add tests demonstrating usage
  4. Update this README with examples

๐Ÿ“š Technical Background

Why "Glass Box" AI?

In clinical settings, unexplainable AI creates liability and trust issues. PACING enforces:

  • Explainability: Models must list contributing factors
  • Auditability: Human-in-the-loop verification of uncertain data
  • Traceability: All decisions have evidence trails

Why Separate "Raw" from "Extracted" Data?

Audio/transcripts are high-dimensional and privacy-sensitive. Structured extractions (medications, events) are:

  • Lower-dimensional (easier to analyze)
  • Easier to anonymize
  • Sufficient for most downstream tasks

This separation enables PROD_MODE's ephemeral raw data policy.

Why Plugin Architecture?

Clinical research requires flexibility:

  • Test multiple transcription APIs
  • Compare different risk models
  • Add domain-specific agents
  • Adapt to new research questions

The interface-based design makes these variations trivial.


๐Ÿ“„ License

MIT License - see LICENSE file for details


๐Ÿค Citation

If you use PACING in research, please cite:

@software{pacing2026,
  title={PACING: Platform for Agentic Clinical Intelligence with Networked Graphs},
  author={Thor Whalen},
  year={2026},
  url={https://github.com/thorwhalen/pacing}
}

โš•๏ธ Clinical Disclaimer: PACING is a research tool. It is not FDA-approved and should not be used as the sole basis for clinical decisions. Always defer to trained clinicians and established clinical protocols.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pacing-0.0.3.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pacing-0.0.3-py3-none-any.whl (36.2 kB view details)

Uploaded Python 3

File details

Details for the file pacing-0.0.3.tar.gz.

File metadata

  • Download URL: pacing-0.0.3.tar.gz
  • Upload date:
  • Size: 37.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pacing-0.0.3.tar.gz
Algorithm Hash digest
SHA256 cffbc30b5d471e45ce431649cd1f382c4b34b3e07a53bb758635e3f9f5943681
MD5 f1b892fdf3db9fdd74d5410f40b45ee7
BLAKE2b-256 3fe3a343c34da6ea05b677df666157c0970d09be4d0b51d8afe84fbdca34b07f

See more details on using hashes here.

File details

Details for the file pacing-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: pacing-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 36.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pacing-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 27ab1ecaf0715419c972d7f1d2a02253091c3e940cbcf8e9c0d69342cea56df9
MD5 3fde237cd8afaeaedcb1ede92ec2da4c
BLAKE2b-256 548cfffe098e77ef7a07d2303c7f1830b41d558c10765d0e0db1fe4a40ea2860

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page