SCAFFOLD (Scalable Coaching and Appreciation Feedback For Optimal Learning and Decision-making) is a precision feedback system for healthcare organizations that can enhance feedback reports, emails, and dashboards with coaching and appreciation messages and visualizations.
Some examples of messages SCAFFOLD can generate are "You reached the top performer benchmark", βYou are not a top performerβ, βYou may have an opportunity to improveβ, βYour team reached the goalβ, and βCongratulations on the exceptionally high quality of care your team has providedβ.
SCAFFOLD is implemented as a software pipeline that processes performance data to produce messages, and requires a knowledge base containing a collection of feedback message templates and prioritization algorithms, among other things. The Precision Feedback Knowledge Base contains examples of messages and algorithms that can be downloaded and customized for use with SCAFFOLD.
This is a Python software project and running SCAFFOLD requires some familiarity with Python and virtual environments. This quick start gives directions using python's built in virtual environment tool venv and Poetry.
git clone https://github.com/Display-Lab/scaffold.git
cd scaffoldUsing venv and pip
Create a virtual environment using python
python --version # make sure you have python 3.12
python -m venv .venvThen activate it.
.venv\Scripts\activate.batsource .venv/bin/activatepip install .\scaffold-sdk #depending on your OS you may need to use pip install ./scaffold-sdk
pip install . # this will install scaffold Alternative: Using uv (for developers)
uv venv
.venv\Scripts\activate.bat # Windows
source .venv/bin/activate # Mac / Linux
uv pip install -e .
uv pip install -e ".[dev]" #to install dev dependenciesIf you are using a knowledge base from a separate repository, clone it in a separate location. For sandbox usecases the corresponding knowledge bases are located in each usecase folder in the sandbox.
Create a copy of the .env.local file available at the root of SCAFFOLD and call it .env.dev and update it by changing path/to/knowledge-base to point to the local knowledge base that you just checked out. (Don't remove the file:// for default_preferences and manifest.)
# .env.dev
default_preferences=file:///Users/bob/knowledge-base/preferences.json
mpm=/Users/bob/knowledge-base/prioritization_algorithms/motivational_potential_model.csv
manifest=file:///Users/bob/knowledge-base/mpog_local_manifest.yaml
...To test if the pipeline is installed and available to use execute the following command
pipeline --helpYou should see the following commands are available:
- batch
- batch-csv
- web
Alternatively you can exexute
python -m scaffold.cli --helpRun SCAFFOLD API using
ENV_PATH=/user/.../.env.dev pipeline webOnce the API is running, you can use Postman or your favorite tool to send a request and review the results. If you have set up the knowledge base for the Sandbox Hospital Quality Dashboard usecase, below is an example curl request that sends an input message to be processed by the API:
curl --data "@sandbox/hospital quality dashboard usecase/data/JSON inputs/input_0b4f8851-6f95-44a0-a771-44959993ea4b.json" http://localhost:8000/createprecisionfeedback/Use the following command to run the pipeline's batch command on some or all json input files in a folder
ENV_PATH=/user/.../.env.dev pipeline batch '/path/to/input/folder/' --max-files 500Use --max-files if you need to limit the number of files to process.
If you need to run the pipeline on a specific json input file use
ENV_PATH=/user/.../.env.dev pipeline batch '/path/to/input/file.json'To run SCAFFOLD on the Sandbox Hospital Quality Dashboard Usecas, use the following command from the root of SCAFFOLD
ENV_PATH=/user/.../.env.dev pipeline batch 'sandbox/hospital quality dashboard usecase/data/JSON inputs' --max-files 10Use the following command to run the pipeline batch-csv command
ENV_PATH=/user/.../.env.dev pipeline batch-csv '/path/to/performance/data/folder' --performance-month 2025-01-01 --max-files 100Use --performance-month to set the performance month for batch_csv command and optional --max-files to limit the cases to process for development.
To run SCAFFOLD on the Sandbox Hospital Quality Dashboard Usecase, use the following command from the root of SCAFFOLD
ENV_PATH=/user/.../.env.dev pipeline batch-csv 'sandbox/hospital quality dashboard usecase/data/tabular inputs' --performance-month 2025-01-01 --max-files 10SCAFFOLD now exposes reusable developer-facing base classes through the scaffold_sdk package namespace.
Example:
from scaffold_sdk import EsteemerThe SDK is split into a standalone distribution named scaffold-sdk in the scaffold-sdk/ folder.
For local development from this repository, install the SDK first:
pip install -e ./scaffold-sdk
pip install -e .For a complete guide to creating and using an external scaffold.esteemer plugin, see src/esteemer/README.md.
Local file path or URL (see .env.remote for github URL formats). All are required.
For now this config file contains the esteemer plugin name and version in a yaml file with a content like:
plugins: scaffold.esteemer: name: mpm_candidate_selector version: "1.0.0"
This configuration file specifies which candidate selection algorithm (plugin) SCAFFOLD should use. We recommend including this file in the knowledge base so that the selected configuration is preserved across knowledge base releases.
manifest: Path to the manifest file that includes differend pieces of the base graph including (causal pathways, message templates, measures and comparators). See manifest configuration for more detail
- default: 6
generate_image: If set to true and the display type is bar chart or line chart, then SCAFFOLD will generate the images and include them as part of the response
- default: True
- default:
WARNING(this is the production default) - note: SCAFFOLD must be run with
log_level=INFOin order to generate the candidate records in the output.
- default: None
- default: monthly
- note: monthly and weekly are supported
- default: 1
- note: for example for a data that is collected quarterly this needs to be set to 3 meas_period_type should be set to monthly. For weekly data it should be set to 1 while meas_period_type is weekly.
- default: 10
- note: performance data for a measure with a count less than the min_count will be removed before processing
These control the elements of the scoring algorithm.
- default: True
- default: True
- default: True
- default: True
The manifest file includes all different pieces that should be loaded to the base graph including causal pathways, message templates, measures and comparators. It is a yaml file which specifies a directory structure containing JSON files for all those different categories.
Each entry consists of a key which is a URL (file:// or https:// or relative, see Uniform Resource Identifier (URI)) and a value which is a file path relative to the url. See manifest examples in the knowledge base.
If the key is a relative path, it must end with a '/'. In that case the key is going to be resolved towards the location of the manifest file by SCAFFOLD.
ENV_PATH=/user/.../.env.dev log_level=INFO use_preferences=True use_coachiness=True use_mi=True generate_image=False uvicorn api:app --workers=5for windows:
$env:ENV_PATH=/user/.../.env.dev; $env:log_level="INFO"; $env:use_preferences="True"; $env:use_coachiness="True"; $env:use_mi="True"; $env:generate_image="False"; uvicorn api:app --workers=5
π
uvicorncan be run with multiple workers. This is useful when testing with a client that can send multiple requests.
SCAFFOLD now returns a single FHIR CommunicationRequest as the primary output model for generated feedback messages.
We selected a CommunicationRequest-first output for the following reasons:
- Clear intent: The pipeline's core result is a communication artifact (message + optional attachments), which maps directly to
CommunicationRequest. - Better interoperability: FHIR-native consumers can ingest a standard resource without custom wrappers.
- Simpler client parsing: Clients no longer need to navigate bundle entries to find the selected message.
- Preserved explainability: SCAFFOLD-specific metadata is still included using FHIR
extensionfields. - Backward-friendly analytics: Candidate records can still be attached when
log_level=INFO.
The response is a JSON object with:
resourceType: CommunicationRequest- Core identity and context:
id,status,subject,authoredOn - Measure context:
about - Message content and artifacts:
payload - SCAFFOLD metadata and signals:
extension - Optional debugging/analysis rows: a nested
candidatesextension under the SCAFFOLD extension (whenlog_level=INFO)
-
Chosen message as first-class resource The selected candidate is represented as a single
CommunicationRequest, because each run produces one final message for one subject and measure. If we decide to select multiple candidates in the future, it would be straightforward to switch from a singleCommunicationRequestto a collection bundle containint multipleCommunicationRequestresources. -
Domain metadata via extensions Template details, causal pathway information, the improvement notation, and (optionally) candidate records are represented as nested extensions under the SCAFFOLD extension URL
https://umich.edu/scaffold/. This keeps domain-specific fields explicit while staying FHIR-compatible.
Custom extensions without a bundled StructureDefinition: SCAFFOLD's output is FHIR-aligned, not a fully conformant FHIR implementation guide/profile. The SCAFFOLD extension is included in the output using its canonical URL, but SCAFFOLD does not generate or host a StructureDefinition for it β not at that URL, and not included with the output.
Example of the SCAFFOLD extension on a CommunicationRequest:
"extension": [
{
"url": "https://umich.edu/fhir/scaffold/StructureDefinition/feedback-metadata",
"extension": [
{
"url": "template_name",
"valueString": "Approach Peer Average"
}
]
}
]A minimal StructureDefinition for this extension could look like:
{
"resourceType": "StructureDefinition",
"id": "scaffold-feedback-metadata",
"url": "https://umich.edu/fhir/scaffold/StructureDefinition/feedback-metadata",
"version": "0.1.0",
"name": "ScaffoldFeedbackMetadata",
"status": "draft",
"kind": "complex-type",
"abstract": false,
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"context": [
{
"type": "element",
"expression": "CommunicationRequest"
}
],
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension"
}
]
}
}π In the future, SCAFFOLD may publish a
StructureDefinitionat this canonical URL so the output can be validated as fully conformant FHIR.
-
Motivating signals in typed FHIR extensions Signals such as performance gap size, trend slope, and streak length are emitted as typed extension values (
valueDecimal,valueInteger) for predictable downstream parsing. -
Portable context in attachments The current performance measure reports and selected comparator measure report are attached as base64-encoded CSV payload attachments so clients can display or audit the exact evidence used in message generation.
-
Optional visual payload When image generation is enabled, the feedback chart is included as a PNG attachment in
payload.
For most clients, this sequence works well:
- Verify
resourceType == "CommunicationRequest". - Read practitioner ID from
subject.reference(for example,Practitioner/<id>). - Read human-readable message from the first
payloaditem withcontentString. - Read measure info from
about[0](display,reference). - Read SCAFFOLD metadata from nested extensions under
url == "https://umich.edu/scaffold/":template_nametemplate_iddisplayacceptable_byperformance_dateimprovementNotationselected_comparator(if present)candidates(iflog_level=INFO)
- Read motivating signals from top-level extensions by URL:
...#PerformanceGapSize...#PerformanceTrendSlope...#StreakLength
- Read optional attachment payloads (
image/png,text/csv) as needed.
{
"resourceType": "CommunicationRequest",
"id": "cr-52e46856-...-Physician-language-01",
"status": "active",
"subject": {
"reference": "Practitioner/52e46856-9a80-41cf-a74d-8cddd753ec56"
},
"about": [
{
"reference": "Physician-language-01",
"type": "Measure",
"display": "Physician informed using clear language"
}
],
"payload": [
{
"contentString": "Your performance is approaching the peer average this month..."
},
{
"contentAttachment": {
"contentType": "image/png",
"data": "<base64>"
}
}
],
"extension": [
{
"url": "https://umich.edu/scaffold/",
"extension": [
{"url": "template_name", "valueString": "Approach Peer Average"},
{"url": "acceptable_by", "valueString": "Social Approach"},
{"url": "improvementNotation", "valueString": "increase"}
]
},
{
"url": "http://example.com/slowmo#PerformanceGapSize",
"valueDecimal": -0.031
}
]
}