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 debuggingPROD_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
- Use PROD_MODE unless debugging
- Implement PHI filtering in the privacy module
- Encrypt stored entities at rest
- Set retention policies for review queues
- Audit access to patient graphs
- 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:
- Define interfaces in
pacing/core/ - Implement in
pacing/impl/ - Add tests demonstrating usage
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cffbc30b5d471e45ce431649cd1f382c4b34b3e07a53bb758635e3f9f5943681
|
|
| MD5 |
f1b892fdf3db9fdd74d5410f40b45ee7
|
|
| BLAKE2b-256 |
3fe3a343c34da6ea05b677df666157c0970d09be4d0b51d8afe84fbdca34b07f
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27ab1ecaf0715419c972d7f1d2a02253091c3e940cbcf8e9c0d69342cea56df9
|
|
| MD5 |
3fde237cd8afaeaedcb1ede92ec2da4c
|
|
| BLAKE2b-256 |
548cfffe098e77ef7a07d2303c7f1830b41d558c10765d0e0db1fe4a40ea2860
|