An open-source, payer-side implementation of the HL7 Da Vinci Documentation Templates and Rules (DTR) Implementation Guide, written twice with identical behavior: once in Go and once in .NET 10. Both serve DTR 2.2.0 and DTR STU 2.0.1 side by side and are tested against the Inferno Da Vinci DTR Payer Server test suites. It is the sister project of davinci-crd-server.
The CMS Interoperability and Prior Authorization Final Rule (CMS-0057-F) requires impacted payers to offer a Prior Authorization API, with most API requirements starting in January 2027, and CMS recommends the Da Vinci Burden Reduction implementation guides for it: CRD (Coverage Requirements Discovery), DTR and PAS (Prior Authorization Support).
DTR is the second step in that flow. When CRD tells the clinician that documentation or prior authorization is needed, a DTR client (a SMART app, or DTR logic built into the EHR) asks the payer for its documentation templates and rules:
- Questionnaires that describe what the payer needs to know,
- CQL Libraries that pre-populate answers from the patient record, so clinicians only fill real gaps,
- ValueSets that the answers draw from.
The payer returns them together from Questionnaire/$questionnaire-package. Some forms are adaptive: the payer
decides the next question from the answers so far, one step at a time, through Questionnaire/$next-question. The
completed QuestionnaireResponse then travels with the prior authorization request. This repository is the payer
side of DTR, meant as a working reference that payers, EHR vendors and integrators can read, run, test against and
build on.
Tested with the Inferno Da Vinci DTR Test Kit v0.18.0 on 12 September 2026. Report summaries are in docs/results/.
| Implementation | Suite | Run | Pass | Fail | Of which known kit issues | Skip or omit | Report |
|---|---|---|---|---|---|---|---|
| Go | DTR v2.2.0 | local, HTTP | 42 | 3 | 3 (ENV-1, KIT-1, KIT-2) | 1 | summary |
| .NET | DTR v2.2.0 | local, HTTP | 42 | 3 | 3 (ENV-1, KIT-1, KIT-2) | 1 | summary |
| Go | DTR v2.0.1 | local, HTTP | 17 | 2 | 2 (KIT-3a, KIT-3b) | 0 | summary |
| .NET | DTR v2.0.1 | local, HTTP | 17 | 2 | 2 (KIT-3a, KIT-3b) | 0 | summary |
| Go | DTR v2.2.0 | public inferno.healthit.gov, HTTPS | 43 | 2 | 2 (KIT-1, KIT-2) | 1 | summary, session |
| Go | DTR v2.0.1 | public inferno.healthit.gov, HTTPS | 17 | 2 | 2 (KIT-3a, KIT-3b) | 0 | summary, session |
| .NET | DTR v2.2.0 | public inferno.healthit.gov, HTTPS | 43 | 2 | 2 (KIT-1, KIT-2) | 1 | summary, session |
| .NET | DTR v2.0.1 | public inferno.healthit.gov, HTTPS | 17 | 2 | 2 (KIT-3a, KIT-3b) | 0 | summary, session |
Every failure in every run is a known kit issue: there are no unexpected failures or errors. The single omit is
v2.2.0 3.4.06 (no contained Binary resources in the sample QuestionnaireResponses), which is not a failure.
Captured on inferno.healthit.gov (DTR test kit v0.18.0) from the sessions linked above. The suite-level red mark comes only from the known kit issues listed in the next section; every other group passes.
Suite overview (Go left, .NET right)
| Go | .NET |
|---|---|
![]() |
![]() |
![]() |
![]() |
v2.2.0 Backend Services, including the token endpoint TLS test, passed over HTTPS
v2.2.0 Questionnaire Operations: 3.1 to 3.6 pass; 3.7 Must Support fails only on KIT-1 and KIT-2 (#130)
v2.0.1 Adaptive Questionnaire Retrieval: 2.01 to 2.06 pass; 2.07 and 2.08 fail with "No resources to validate." because an earlier test clears their data (#131)
The failures in the "known kit issues" column are listed in the next section. They are counted as failures, not hidden. Local runs use plain HTTP, so the v2.2.0 test that checks the token endpoint for TLS 1.2 or higher cannot pass there; on the public Inferno instance the servers sit behind an HTTPS tunnel and that test runs over TLS. Passing Inferno is a conformance signal, not a certification.
This is the open state as of 12 September 2026 with kit v0.18.0. The servers follow the DTR profiles rather than work around the kit, so these tests fail for any conformant server until the kit is fixed.
| Suite | Test | What happens | Upstream issue |
|---|---|---|---|
| v2.2.0 | dtr_v220_payer_questionnaire_base_must_support |
The generated must support list requires Questionnaire.extension:assemble-expectation, but the DTR 2.2.0 profiles (dtr-base-questionnaire, dtr-std-questionnaire, dtr-questionnaire-adapt) set that slice to max 0. Adding the extension makes this test pass and the package and $next-question profile validation tests fail. |
davinci-dtr-test-kit#130 |
| v2.2.0 | dtr_v220_payer_questionnaire_adaptive_search_must_support |
The list requires item, the item.* elements and assemble-expectation, but dtr-questionnaire-adapt-search 2.2.0 sets Questionnaire.item and assemble-expectation to max 0. |
davinci-dtr-test-kit#130 |
| v2.0.1 | payer_server_adaptive_response_bundles_validation_test |
An earlier test in the adaptive group (the adaptive expressions test) clears the stored adaptive questionnaire bundles, so this test fails with "No resources to validate." | davinci-dtr-test-kit#131 |
| v2.0.1 | payer_server_adaptive_response_search_validation_test |
Same cleared data; fails with "No questionnaire bundles to validate." | davinci-dtr-test-kit#131 |
A v2.0.1 run with the input retrieval_method=Static skips every adaptive test, including these two, but also
skips the adaptive coverage, so the published runs keep Both.
One IG snapshot quirk is avoided rather than listed: the DTR 2.0.1 dtr-qpackage-output-parameters snapshot copies
the Bundle invariants bdl-3 and bdl-4 onto the Parameters wrapper, so any package Bundle wrapped in Parameters
fails validation. For a single package without warnings the v2.0.1 base returns the bare Bundle, which the 2.0.1
operation allows. The evidence for every entry is in spec/SPEC.md section 12 and
harness/known_kit_issues.json.
- Two DTR versions in one process: DTR 2.2.0 under
/v220/fhirand STU 2.0.1 under/v201/fhir, each with its own CapabilityStatement and payer content. - SMART Backend Services authorization:
.well-known/smart-configurationand a token endpoint forclient_credentialswithprivate_key_jwt. Client assertions must be signed with RS384 or ES384 by a key from a trusted JWK Set (refetched once on an unknownkid), withissandsubequal to a registered client id,audequal to the token endpoint,expat most five minutes ahead and an unusedjti. Access tokens are opaque and short-lived. Questionnaire/$questionnaire-package: resolves the Coverage to an active member (422 otherwise), selects Questionnaires by canonical (with optional version), by context id or by order code (DeviceRequest, ServiceRequest and the other DTR order types), removes duplicates and reports unknown canonicals as a warningoutcome. Each package Bundle holds the Questionnaire first, then (2.2.0) an initial in-progress QuestionnaireResponse withqr-coverage,intendedUseandqr-context, then its Libraries and ValueSets.- CQL packaging: Libraries carry base64 CQL and ELM, Questionnaire expressions use CQL with an ELM
alternativeExpression(2.2.0), and everycqf-libraryandanswerValueSetreference is versioned and resolves inside the same bundle. - Adaptive
Questionnaire/$next-question: stateless. The server validates the posted QuestionnaireResponse (contained form, link ids, answer types, repeats, answer options, enabled items; item issues are returned together with anexpression), applies the flow rules, and returns the response with the contained form grown by the next questions. The status becomescompletedwhen nothing new is shown and every required item is answered. ValueSet/$expandby GET or POST, returning the expanded ValueSet with a UTC timestamp, andQuestionnaire/$log-questionnaire-errors, recorded in the audit log.- FHIR errors as OperationOutcomes: 400 for invalid input, 401 with
WWW-Authenticate: Bearer, 404 for unknown routes and ValueSets, 422 for source data errors such as an unknown or inactive coverage. - Sample payer content as data: a hospital bed Questionnaire (HCPCS E0250) with CQL pre-population and
enableWhenlogic, and an adaptive sleep study Questionnaire (CPT 95810), with their Libraries, ValueSets, payer directory and adaptive flow in spec/content/. All members and identifiers are synthetic. - Reference engine and golden examples:
harness/dtrgen/engine.pyis an executable reading of the spec. It generates the content, the Inferno inputs and the golden exchanges inspec/examples/, which the Go and .NET unit tests replay through their HTTP handlers. - External base URL per request from
DTR_PUBLIC_BASE_URLorX-Forwarded-ProtoandX-Forwarded-Host, so advertised endpoints,fullUrls and the adaptive endpoint stay correct behind proxies and tunnels. - Audit log: one JSON line per request in a daily file. Access tokens and client assertions are never written.
- Library plus host: no global state and a configurable route prefix, so the DTR module can be mounted next to CRD and PAS in one process. Docker images run as a non-root user.
Each implementation is a DTR library, a shared package for cross-cutting concerns and a thin host.
flowchart LR
CLIENT["DTR client: SMART app or EHR<br/>(Inferno during testing)"]
CONTENT[("spec/content<br/>payer directory, Questionnaires,<br/>Libraries, ValueSets, adaptive flows")]
JWKS[("Client JWK Sets")]
subgraph proc["DTR server process"]
direction TB
HOST["Host<br/>Go: cmd/dtr-server<br/>.NET: Nirmitee.DaVinci.Dtr.Host"]
subgraph lib["DTR library (Go: dtr, .NET: Nirmitee.DaVinci.Dtr)"]
DISC["CapabilityStatement and<br/>SMART configuration"]
TOKEN["Token endpoint and<br/>bearer checks"]
PKG["$questionnaire-package"]
NEXT["$next-question"]
TERM["ValueSet/$expand,<br/>$log-questionnaire-errors"]
STORE["Content store with<br/>per-request FHIR base"]
end
subgraph shared["Shared (Go: internal/shared, .NET: Nirmitee.DaVinci.Common)"]
AUTH["Client assertion verifier,<br/>JWKS cache, jti replay cache"]
FJSON["FHIR JSON helpers"]
AUD["JSONL audit log"]
end
end
CLIENT -- "token request with client assertion" --> HOST
CLIENT -- "FHIR operations with bearer token" --> HOST
HOST --> lib
TOKEN --> AUTH
AUTH -. "fetch keys" .-> JWKS
PKG --> STORE
NEXT --> STORE
TERM --> STORE
STORE --> CONTENT
PKG --> FJSON
lib --> AUD
The Go internal/shared package and the .NET Nirmitee.DaVinci.Common project are copies of the ones in the CRD
server with the same public API, plus the SMART client assertion verifier and token store that DTR needs. That keeps
the servers independent today and makes the shared module a mechanical extraction later.
How DTR fits with the CRD and PAS servers:
flowchart TB
subgraph flow["Prior authorization flow (one repository and deployable server per IG)"]
CRD["davinci-crd-server<br/>CDS Hooks: coverage, PA and documentation needs"]
DTR["davinci-dtr-server (this repository)<br/>Questionnaires, CQL, adaptive forms"]
PAS["davinci-pas-server (planned)<br/>prior authorization submit and inquire"]
end
COMMON["Shared module (planned)<br/>JWT and JWKS auth, FHIR helpers, audit log, config"]
COMBINED["Combined Burden Reduction server (planned)<br/>/crd, /dtr and /pas in one process"]
CRD -- "documentation needed: which Questionnaires" --> DTR
DTR -- "completed QuestionnaireResponse" --> PAS
CRD --> COMMON
DTR --> COMMON
PAS --> COMMON
CRD --> COMBINED
DTR --> COMBINED
PAS --> COMBINED
Mounting the DTR module in your own host:
cfg, err := dtr.LoadConfig("", os.Getenv)
if err != nil { log.Fatal(err) }
cfg.Prefix = "/dtr"
handler, err := dtr.NewHandler(cfg)
if err != nil { log.Fatal(err) }
mux.Handle("/dtr/", handler) // /dtr/v220/fhir, /dtr/v201/fhir, /dtr/healthbuilder.Services.AddDtr(builder.Configuration);
app.MapDtr("/dtr"); // /dtr/v220/fhir/..., /dtr/v201/fhir/..., /dtr/healthThe full behavior contract both implementations follow is spec/SPEC.md. Implementation details are in go/README.md and dotnet/README.md.
git clone https://github.com/Nirmitee-tech/davinci-dtr-server.git
cd davinci-dtr-serverRequires Go 1.26.
cd go
go run ./cmd/dtr-server
curl http://localhost:8183/v220/fhir/metadataRequires the .NET 10 SDK.
cd dotnet
dotnet run --project src/Nirmitee.DaVinci.Dtr.Host -- --urls http://0.0.0.0:8084
curl http://localhost:8084/v220/fhir/metadataBuild from the repository root so spec/content is in the build context.
docker build -f go/Dockerfile -t davinci-dtr-server-go .
docker run --rm -p 8183:8183 davinci-dtr-server-go
docker build -f dotnet/Dockerfile -t davinci-dtr-server-dotnet .
docker run --rm -p 8084:8084 davinci-dtr-server-dotnetInside a container http://localhost is the container itself. When the trusted JWK Sets are served on the host (as
with a local Inferno on port 8180), rewrite them: -e DTR_URL_REWRITES="http://localhost:8180|http://host.docker.internal:8180"
for Go, or -e Dtr__JwksUrlRewrites__0__From=http://localhost:8180 -e Dtr__JwksUrlRewrites__0__To=http://host.docker.internal:8180
for .NET.
Discovery needs no token:
curl -s http://localhost:8183/v220/fhir/.well-known/smart-configurationThe v2.2.0 operations need an access token from the token endpoint, which requires a signed client assertion. For a quick local try, start the server with a test-only static token on the v2.0.1 base (static tokens are off by default; never enable one outside local testing, see SECURITY.md) and post the synthetic static package request:
# start the server with, for example: DTR_V201_STATIC_TOKENS=local-test-token go run ./cmd/dtr-server
curl -s -X POST 'http://localhost:8183/v201/fhir/Questionnaire/$questionnaire-package' \
-H "Authorization: Bearer $LOCAL_TEST_TOKEN" \
-H 'Content-Type: application/fhir+json' \
--data @testdata/v201/initial_static_questionnaire_request.jsonThe response is the hospital bed package: the Questionnaire, its CQL Library and its ValueSet in one Bundle.
Environment variables shared by both implementations:
| Variable | Default | Meaning |
|---|---|---|
DTR_LISTEN |
:8183 (Go), :8084 (.NET) |
listen address; -listen (Go) or --urls (.NET) overrides it |
DTR_PUBLIC_BASE_URL |
unset | fixed external origin, for example https://<tunnel-host> |
DTR_CONTENT_DIR |
spec/content |
payer content directory |
DTR_CLIENT_IDS |
nirmitee-dtr-inferno |
accepted client ids (assertion iss and sub), comma separated |
DTR_TRUSTED_JWKS_URLS |
local and public Inferno JWK Sets | JWK Sets used to verify client assertions, comma separated |
DTR_JWKS_FILE |
unset | local JWK Set checked before the URLs |
DTR_TOKEN_TTL_SECONDS |
300 |
access token lifetime |
DTR_V201_STATIC_TOKENS |
empty (opt-in) | extra bearer tokens accepted on the v2.0.1 base; the Inferno v2.0.1 test data needs nirmitee-dtr-v201-static-token for local test runs only, never set it on a real deployment |
DTR_AUDIT_DIR |
data/dtr-audit |
audit log directory; lines go to {dir}/YYYY-MM-DD.jsonl |
The default trusted JWK Sets are the Inferno SMART App Launch kit keys (/custom/smart_stu2/.well-known/jwks.json)
and the inferno_core keys (/jwks.json) on http://localhost:8180 and https://inferno.healthit.gov/suites.
Replace them, the client id and the static token with your real clients in production.
The v2.0.1 base also accepts tokens issued by either base and the static tokens, because the v2.0.1 suite sends a bearer token from its inputs and never requests one itself.
Implementation-specific settings:
| Setting | Go | .NET |
|---|---|---|
| Config file | -config path or DTR_CONFIG_FILE (YAML or JSON) |
appsettings.json, Dtr section |
| Route prefix | DTR_PREFIX |
Dtr__PathPrefix |
| Outbound JWKS URL rewrites | DTR_URL_REWRITES (from|to pairs) |
Dtr__JwksUrlRewrites__N__From, __To |
| JWT clock skew | DTR_CLOCK_SKEW_SECONDS |
Dtr__ClockSkewSeconds |
| Log level | DTR_LOG_LEVEL |
Logging__LogLevel__Default |
| Operations without a token (development only) | DTR_AUTH_DISABLED=true |
not available |
The Docker images set DTR_AUDIT_DIR=/data/dtr-audit and point DTR_CONTENT_DIR at the content copied into the
image.
The test kit is not part of this repository. You need Docker with Compose 2.24.4 or newer, and Python 3.
# 1. clone the Inferno DTR test kit in the repository root (the folder is gitignored)
git clone https://github.com/inferno-framework/davinci-dtr-test-kit.git
# 2. add davinci-dtr-test-kit/docker-compose.override.yml (below), then start the kit on port 8180
(cd davinci-dtr-test-kit && COMPOSE_PROJECT_NAME=inferno-dtr sh setup.sh && docker compose -p inferno-dtr up -d)
# 3. run a server natively on the host (Go on 8183 or .NET on 8084), then run the suites
python3 harness/run_suite.py --version v220 --impl go
python3 harness/run_suite.py --version v201 --impl goThe override publishes Inferno on http://localhost:8180, where the harness and the servers' default trusted JWK
Sets expect it, and gives the HL7 validator enough memory for both DTR IG versions:
name: inferno-dtr
services:
inferno:
environment:
INFERNO_HOST: http://localhost:8180
worker:
environment:
INFERNO_HOST: http://localhost:8180
nginx:
ports: !override
- "8180:80"
hl7_validator_service:
mem_limit: 6g
environment:
JAVA_TOOL_OPTIONS: -Xmx5g--impl dotnet targets port 8084. Useful options: --groups 1,2 to run selected groups, --input NAME=VALUE to
override an input (for example retrieval_method=Static on v2.0.1), --server-base for the origin Inferno should
call, and --inferno https://inferno.healthit.gov/suites to drive the public instance. Reports are written to
harness/reports/. The exit status is 0 for no failures, 1 for unexpected failures, 2 for a harness problem and 3
when the only failures are the known kit issues. python3 harness/build_dtr_content.py rebuilds the content, the
test inputs and the golden examples.
To run the TLS test, expose the server over HTTPS, for example with ngrok http 8183, start the server with
DTR_PUBLIC_BASE_URL=https://<ngrok-host>, and run the suites on the public Inferno instance with
--server-base https://<ngrok-host>. The step-by-step guide is harness/tls_public_run.md.
go/ Go implementation: dtr/ library (content store, engine), cmd/dtr-server host, internal/shared
dotnet/ .NET 10 implementation: Nirmitee.DaVinci.Dtr library, Dtr.Host, Common, xUnit tests
spec/ SPEC.md behavior contract, content/ payer content and CQL sources, examples/ golden exchanges
testdata/ synthetic Inferno inputs for the v2.2.0 and v2.0.1 suites
harness/ suite runner, content generator and reference engine, known kit issues, TLS run guide
docs/results/ curated Inferno report summaries
- PAS server: Da Vinci Prior Authorization Support server (submit, inquire, subscriptions and notifications), Go and .NET, tested against the Inferno PAS server suites.
- Shared module: replace the copies of the shared code in the CRD and DTR servers with one module for JWT and JWKS auth, SMART Backend Services, FHIR helpers, the audit log and configuration.
- Combined server: one Burden Reduction host that mounts CRD, DTR and PAS under their own prefixes.
- Known kit issues: remove each entry once the upstream test kit fix is released and the suites are rerun.
Contributions are welcome. Behavior changes start in spec/SPEC.md and land in both implementations. See
CONTRIBUTING.md for setup, tests and the pull request checklist, and follow the
Code of Conduct. Report security issues privately as described in SECURITY.md.
All data in this repository is synthetic. Please never post real patient data in issues or pull requests.
Apache License 2.0. See LICENSE and NOTICE.
HL7, FHIR and the FHIR [FLAME DESIGN] are the registered trademarks of Health Level Seven International and their use does not constitute endorsement by HL7. The Inferno test kit is developed by the Inferno Framework team and is not redistributed here.
Built by Nirmitee.io.






