feat(bcda): close the upstream-review gaps — partial exports, claim identity across systems, attribution checks, filter grammar, vendored behaviour
Some checks failed
CI / lint (push) Successful in 54s
CI / notebooks-smoke (push) Successful in 1m43s
CI / test (push) Failing after 2m56s
Infra CI / zotero (push) Successful in 15s
Infra CI / notebooks (push) Successful in 1m1s
Infra CI / api (push) Successful in 1m7s
Infra CI / docs (push) Successful in 1m50s
Infra CI / llm (push) Successful in 49s
Infra CI / mc (push) Failing after 35s
Infra CI / bcda (push) Successful in 1m42s
CI / lint (pull_request) Successful in 45s
CI / notebooks-smoke (pull_request) Successful in 1m38s
CI / test (pull_request) Failing after 2m53s
Infra CI / mc (pull_request) Failing after 30s
Infra CI / zotero (pull_request) Successful in 16s
Infra CI / notebooks (pull_request) Successful in 58s
Infra CI / docs (pull_request) Successful in 16s
Infra CI / api (pull_request) Successful in 15s
Infra CI / bcda (pull_request) Successful in 21s
Infra CI / llm (pull_request) Successful in 17s
Some checks failed
CI / lint (push) Successful in 54s
CI / notebooks-smoke (push) Successful in 1m43s
CI / test (push) Failing after 2m56s
Infra CI / zotero (push) Successful in 15s
Infra CI / notebooks (push) Successful in 1m1s
Infra CI / api (push) Successful in 1m7s
Infra CI / docs (push) Successful in 1m50s
Infra CI / llm (push) Successful in 49s
Infra CI / mc (push) Failing after 35s
Infra CI / bcda (push) Successful in 1m42s
CI / lint (pull_request) Successful in 45s
CI / notebooks-smoke (pull_request) Successful in 1m38s
CI / test (pull_request) Failing after 2m53s
Infra CI / mc (pull_request) Failing after 30s
Infra CI / zotero (pull_request) Successful in 16s
Infra CI / notebooks (pull_request) Successful in 58s
Infra CI / docs (pull_request) Successful in 16s
Infra CI / api (pull_request) Successful in 15s
Infra CI / bcda (pull_request) Successful in 21s
Infra CI / llm (pull_request) Successful in 17s
Closes #1293 #1294 #1295 #1296 #1297 #1298 #1299 #1300 (upstream review docs/superpowers/specs/2026-10-25-bcda-upstream-review.md). Partial exports (#1293). The manifest's `error` list is downloaded as OperationOutcome[.N].ndjson with resource_type=OperationOutcome; every URL is attempted and failures are recorded per file, then raised once as DownloadError. A job with error files is `partial`: it still reconciles (its rows are real) but never becomes the watermark, check_since warns `partial_job`, the Store page lists the issues and `stack bcda errors` prints them. Claim identity (#1294), per bcda.cms.gov "Matching multiple EOBs to the same claim": three new header columns — clm_cntl_num_match (VMS leading 1 stripped on SharedSystem DME), clm_natural_key (Part D events without a control number: bene · line from-date · provider qualifier · service provider · RX number · fill number) and superseded_by — plus prvdr_srvc_id_qlfyr_cd. bcda.sync keys claims on match → control number → natural key → id, marks every claim another current claim names in `related` as superseded (chain walked), counts `superseded`, and a new invariant forbids a live claim being named predecessor by another live claim. The connector dedupes on the same identity and drops superseded rows. Partitions and current tables flattened before these columns existed get them as NULLs. Attribution (#1295): `attribution_expired` (block) from expiration_dates, `attribution_floor` (warn) from the earliest last_attribution_update recorded on job rows (new column), and the server's 404/400/429 texts explained by explain_server_error. Adoption (#1296): completed_jobs() drops jobs older than the 24 h file retention; downloads no longer stop at the first failed file. _typeFilter (#1297): build_type_filter follows bcda/fhir/param.go — outcome lists, service-date earliest and latest bounds with eq/gt/ge/lt/le on YYYY[-MM[-DD]], no comma lists, no conflicting prefixes; CLI and Exports page expose them. Runout (#1298): `runout_window` warn after July; population column on the Sync page ledger. Vendoring (#1299): the manifest now pins bcda-app's param.go, bluebutton.go, service.go, constants, models and prod.yml, the docs source (CMSgov/bcda-static-site: filter/access/migrate/PAC/FAQ pages and announcements) and the PAC and unmapped-fields dictionaries. Docs (#1300): the reconciliation design record states the server's window `(since, transactionTime]`, the lookback bound, the silent exclusions, re-loads, partial exports and the identity rules. refs #1139
This commit is contained in:
52
dev/vendor/MANIFEST.toml
vendored
52
dev/vendor/MANIFEST.toml
vendored
@@ -25,6 +25,35 @@ files = [
|
||||
"shared_files/gzip_feature_test/test_gzip_encoded.ndjson",
|
||||
"shared_files/gzip_feature_test/test_no_encoding.ndjson",
|
||||
"conf/configs/sandbox.yml",
|
||||
"conf/configs/prod.yml",
|
||||
"bcda/fhir/param.go",
|
||||
"bcda/client/bluebutton.go",
|
||||
"bcda/service/service.go",
|
||||
"bcda/constants/constants.go",
|
||||
"bcda/constants/types.go",
|
||||
"bcda/models/models.go",
|
||||
]
|
||||
|
||||
[[source]]
|
||||
name = "bcda-static-site"
|
||||
kind = "github"
|
||||
repo = "CMSgov/bcda-static-site"
|
||||
ref = "main"
|
||||
commit = "cd14babf8eca7247c35a6ba63ae5c221764ba236"
|
||||
license = "LICENSE"
|
||||
files = [
|
||||
"LICENSE",
|
||||
"src/_pages/api-documentation/filter-claims-data.md",
|
||||
"src/_pages/api-documentation/access-claims-data.md",
|
||||
"src/_pages/api-documentation/how-to-migrate-v3.md",
|
||||
"src/_pages/bcda-data/partially-adjudicated-claims-data.md",
|
||||
"src/_pages/bcda-data/comparison-bcda-cclf-files.md",
|
||||
"src/_pages/support/faqs.md",
|
||||
"src/_announcements/2025-py-data-limit-ended.md",
|
||||
"src/_announcements/bcda-data-limited-in-jan-2026-for-yearly-updates.md",
|
||||
"src/_announcements/bcda-launches-v3.md",
|
||||
"src/_announcements/bcda-v3-expanded-access-general-availability.md",
|
||||
"src/_announcements/bcda-performance-improvement-to-speed-up-job-requests.md",
|
||||
]
|
||||
|
||||
[[source]]
|
||||
@@ -34,6 +63,8 @@ base = "https://bcda.cms.gov/assets/downloads/"
|
||||
files = [
|
||||
"BCDA_v3_Data_Dictionary.xlsx",
|
||||
"BCDA_v2_Data_Dictionary.xlsx",
|
||||
"BCDA_Partially_Adjudicated_Data_Dictionary.xlsx",
|
||||
"unmapped-fields-between-cclf-and-bcda.xlsx",
|
||||
"v3_Patient.ndjson",
|
||||
"v3_Coverage.ndjson",
|
||||
"v3_ExplanationOfBenefit.ndjson",
|
||||
@@ -44,10 +75,17 @@ files = [
|
||||
"bcda-app/bcda/api/requests.go" = "3ae1b33dec6e0d9c2c93352f3da7a713ffeeda545e5be4d00643b43e65569456"
|
||||
"bcda-app/bcda/api/v3/api.go" = "18a1dbc627d01a4d9f53113a6e45671494445da3fb8f9c877437f4ed8d62d871"
|
||||
"bcda-app/bcda/api/v3/api_test.go" = "97c0ca1d0af2f5fe2fa68102b8c6d6d9709f2775ed58cd291886e62e66cd869c"
|
||||
"bcda-app/bcda/client/bluebutton.go" = "e70841e241d3b1a8a6b9dff6d029fdfc28d87f007ca86a4e7e0e202d3b729d62"
|
||||
"bcda-app/bcda/constants/constants.go" = "bbfbcaf66ed1e9d4cf8160a2d3b18c9cc38a47444868181e36643e3d8f74fdef"
|
||||
"bcda-app/bcda/constants/types.go" = "dafa4f745ef57c71289d65e05b642dd9e165db98a53ec2f3e6865ce2c9bf2b8c"
|
||||
"bcda-app/bcda/fhir/param.go" = "693c26317f01741bfc5bd62b7955438858459e607b37e3caa8a56bbff7088ada"
|
||||
"bcda-app/bcda/models/models.go" = "496a4c0212de5a7f86930c7b00b8d880f89b6708b29da2e99d015d46ef3b25df"
|
||||
"bcda-app/bcda/responseutils/v3/writer.go" = "d5bb9e8dfcad75bc3cf5cadf0f811e1b16d4e09cd0755970517ab3ab0867f56c"
|
||||
"bcda-app/bcda/responseutils/v3/writer_test.go" = "9056a6f0c4e6dc747375e232a982064d3e616b9832baa90f94c7bbe241bd58bc"
|
||||
"bcda-app/bcda/service/service.go" = "f98c770026ec19cea0543937dc402f8c59a4be5415fac9632925ef8e78465158"
|
||||
"bcda-app/bcda/web/middleware/validation.go" = "dc4dfdf730c24961746f0b8f576b5a1cf912d7cec5e237401126738e6a82ed6a"
|
||||
"bcda-app/bcda/web/middleware/validation_test.go" = "c16f268ae3186b6881aa06ef1374d93117a969e87eafd13edcde0ac766a6b5e1"
|
||||
"bcda-app/conf/configs/prod.yml" = "e644db4b609ce3f72a2b281db6a1bd24fe9648e8013a1de570710500173f0bc5"
|
||||
"bcda-app/conf/configs/sandbox.yml" = "a954b23194796658ddcfc8282ef3de488356a6076ed84266cfce6959df68e7f5"
|
||||
"bcda-app/shared_files/gzip_feature_test/corrupt_gz_file.ndjson" = "41b355a94e96ef4ed92c6fde19ca67d7341a5f95bd67e6b1e8a65f1b83be2b36"
|
||||
"bcda-app/shared_files/gzip_feature_test/test_gzip_encoded.ndjson" = "b5a7caf480fec615a1487f7bde4974f373395d98f245598c146844b1003f9a8f"
|
||||
@@ -55,8 +93,22 @@ files = [
|
||||
"bcda-app/swagger.json" = "3565bdea5a5f0817b3abb95432645ef15e32179dbc18fc2a4d3de434d293e03f"
|
||||
"bcda-app/test/postman_test/BCDA_V3_Tests.postman_collection.json" = "fbb0c523f078f215bee178a9010fd331287c3b52ea6d712ea328e392ed77c237"
|
||||
"bcda-app/test/postman_test/sandbox.postman_environment.json" = "6d1866a8ae06185390ff80180bff0c38d93f591d71066d51b6a1f3a80cf6377c"
|
||||
"bcda-site/BCDA_Partially_Adjudicated_Data_Dictionary.xlsx" = "9451a3988d7d1c8c3375c5a64c370bd19f35abc0febe3c0c8d36927d4bb1f233"
|
||||
"bcda-site/BCDA_v2_Data_Dictionary.xlsx" = "9c91eae07869f573c5578da9606336b00f1768180a947e2c4275864877f88810"
|
||||
"bcda-site/BCDA_v3_Data_Dictionary.xlsx" = "335547374a66ce88b4c88cf0657ec09f5e731af6b7ede998e0b162f6594ec3bc"
|
||||
"bcda-site/unmapped-fields-between-cclf-and-bcda.xlsx" = "9818933895ce58bed27f9208758f7edd0594a6f6bc48770726b8f89c6af01313"
|
||||
"bcda-site/v3_Coverage.ndjson" = "cbe8820255f7a15d896b9098a33422d1e012cacc059316ac4894272132ff2986"
|
||||
"bcda-site/v3_ExplanationOfBenefit.ndjson" = "fafd76d16c335de44d1b28c388ac3de873809d3b2c429612ec6b8741db0fa82f"
|
||||
"bcda-site/v3_Patient.ndjson" = "7eb427266676415c6beee0fa705d7ad31d2335971f711bdfe0c63712392ba351"
|
||||
"bcda-static-site/LICENSE" = "6d489af6292662d9e36d34ce49423784984a5f6e41d7b58f49b01264df59fa03"
|
||||
"bcda-static-site/src/_announcements/2025-py-data-limit-ended.md" = "ff8014d6f2bbe232d176c62ea7999ad941fb584444242524b2e3615b2b8203d6"
|
||||
"bcda-static-site/src/_announcements/bcda-data-limited-in-jan-2026-for-yearly-updates.md" = "bcf226627c84fe91c99f47c3987b426806ed458da474b7ce38e1a3029209410e"
|
||||
"bcda-static-site/src/_announcements/bcda-launches-v3.md" = "efadb9f0a157844ff5b818273eb53fb5c719f12db35bc0f80384fffc3ab4f99c"
|
||||
"bcda-static-site/src/_announcements/bcda-performance-improvement-to-speed-up-job-requests.md" = "9729ffa12daac14a6ae398fb841a0b0df414046cd5fb625ecf1eb862069313ba"
|
||||
"bcda-static-site/src/_announcements/bcda-v3-expanded-access-general-availability.md" = "e0404747067c0c2723ec49b357eba68f5b3d8f934bf4c9221b1b3810cbbf44ff"
|
||||
"bcda-static-site/src/_pages/api-documentation/access-claims-data.md" = "c48ef7cbc7a9fd54161758245d8de0ce3b81f030453da7ecb2f37ccc4eea6e57"
|
||||
"bcda-static-site/src/_pages/api-documentation/filter-claims-data.md" = "e098ac1af2283796a1b64eb9b30c396a98f281e7a13de0f74a926f13c7412a69"
|
||||
"bcda-static-site/src/_pages/api-documentation/how-to-migrate-v3.md" = "b07d928b182e41cfef65eb50a9d04d846cbca2120b8d4eab472b4d2df2689d76"
|
||||
"bcda-static-site/src/_pages/bcda-data/comparison-bcda-cclf-files.md" = "c87000f64a7a16e1180d654881ce561ad7c63885f9228c8d78be10e703792a28"
|
||||
"bcda-static-site/src/_pages/bcda-data/partially-adjudicated-claims-data.md" = "cc65fa79d657b6c41d5c989b830bf7e64adb62270cd7fbc0728123265d5423d8"
|
||||
"bcda-static-site/src/_pages/support/faqs.md" = "f8655fb91ba744485a70adcf08491e04ee059ace7e7054769059b75cfbfed875"
|
||||
|
||||
603
dev/vendor/bcda-app/bcda/client/bluebutton.go
vendored
Normal file
603
dev/vendor/bcda-app/bcda/client/bluebutton.go
vendored
Normal file
@@ -0,0 +1,603 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccoveille/go-safecast"
|
||||
backoff "github.com/cenkalti/backoff/v4"
|
||||
|
||||
fhirClient "github.com/CMSgov/bcda-app/bcda/client/fhir"
|
||||
"github.com/CMSgov/bcda-app/bcda/constants"
|
||||
|
||||
"github.com/CMSgov/bcda-app/bcda/fhir"
|
||||
"github.com/CMSgov/bcda-app/bcda/utils"
|
||||
"github.com/CMSgov/bcda-app/bcdaworker/queueing/worker_types"
|
||||
"github.com/CMSgov/bcda-app/conf"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
var logger logrus.FieldLogger
|
||||
|
||||
const (
|
||||
clientIDHeader = "BULK-CLIENTID"
|
||||
jobIDHeader = "BULK-JOBID"
|
||||
TransactionIDHeader = "TRANSACTIONID"
|
||||
)
|
||||
|
||||
// BlueButtonConfig holds the configuration settings needed to create a BlueButtonClient
|
||||
// TODO (BCDA-3755): Move the other env vars used in NewBlueButtonClient to this struct
|
||||
type BlueButtonConfig struct {
|
||||
BBServer string
|
||||
BBBasePath string
|
||||
}
|
||||
|
||||
// NewConfig generates a new BlueButtonConfig using various environment variables.
|
||||
func NewConfig(basePath string) BlueButtonConfig {
|
||||
var server string
|
||||
if basePath == constants.BFDV3Path {
|
||||
server = conf.GetEnv("V3_BB_SERVER_LOCATION")
|
||||
} else {
|
||||
server = conf.GetEnv("BB_SERVER_LOCATION")
|
||||
}
|
||||
return BlueButtonConfig{
|
||||
BBServer: server,
|
||||
BBBasePath: basePath,
|
||||
}
|
||||
}
|
||||
|
||||
type ClaimsWindow struct {
|
||||
Earliest time.Time
|
||||
Latest time.Time
|
||||
}
|
||||
|
||||
type APIClient interface {
|
||||
GetExplanationOfBenefit(jobData worker_types.JobEnqueueArgs, patientID string, claimsWindow ClaimsWindow) (*fhir.Bundle, error)
|
||||
GetPatient(jobData worker_types.JobEnqueueArgs, patientID string) (*fhir.Bundle, error)
|
||||
GetCoverage(jobData worker_types.JobEnqueueArgs, beneficiaryID string) (*fhir.Bundle, error)
|
||||
GetPatientByMbi(jobData worker_types.JobEnqueueArgs, mbi string) (string, error)
|
||||
GetClaim(jobData worker_types.JobEnqueueArgs, mbi string, claimsWindow ClaimsWindow) (*fhir.Bundle, error)
|
||||
GetClaimResponse(jobData worker_types.JobEnqueueArgs, mbi string, claimsWindow ClaimsWindow) (*fhir.Bundle, error)
|
||||
}
|
||||
|
||||
type BlueButtonClient struct {
|
||||
client fhirClient.Client
|
||||
|
||||
maxTries uint64
|
||||
retryInterval time.Duration
|
||||
|
||||
bbServer string
|
||||
BBBasePath string
|
||||
}
|
||||
|
||||
// Ensure BlueButtonClient satisfies the interface
|
||||
var _ APIClient = &BlueButtonClient{}
|
||||
|
||||
func NewBlueButtonClient(config BlueButtonConfig) (*BlueButtonClient, error) {
|
||||
certFile := conf.GetEnv("BB_CLIENT_CERT_FILE")
|
||||
keyFile := conf.GetEnv("BB_CLIENT_KEY_FILE")
|
||||
pageSize := utils.GetEnvInt("BB_CLIENT_PAGE_SIZE", 0)
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not load Blue Button keypair")
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}
|
||||
|
||||
if strings.ToLower(conf.GetEnv("BB_CHECK_CERT")) != "false" {
|
||||
caFilePaths := strings.Split(conf.GetEnv("BB_CLIENT_CA_FILE"), ",")
|
||||
caCertPool := x509.NewCertPool()
|
||||
|
||||
for _, caFile := range caFilePaths {
|
||||
caCert, err := os.ReadFile(filepath.Clean(caFile))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read CA file")
|
||||
}
|
||||
|
||||
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
|
||||
return nil, errors.New("could not append CA certificate(s)")
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
} else {
|
||||
tlsConfig.InsecureSkipVerify = true // #nosec G402 this is only used in localdev/ci testing
|
||||
logger.Warn("Blue Button certificate check disabled")
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
// Ensure that we have compression enabled. This allows the transport to request for gzip content
|
||||
// and handle the decompression transparently.
|
||||
// See: https://golang.org/src/net/http/transport.go?s=3396:10950#L182 for more information
|
||||
DisableCompression: false,
|
||||
}
|
||||
var timeout int
|
||||
if timeout, err = strconv.Atoi(conf.GetEnv("BB_TIMEOUT_MS")); err != nil {
|
||||
logger.Warn(errors.Wrap(err, "Could not get Blue Button timeout from environment variable; using default value of 10000."))
|
||||
timeout = 10000
|
||||
}
|
||||
|
||||
hl := &httpLogger{transport, logger}
|
||||
httpClient := &http.Client{Transport: hl, Timeout: time.Duration(timeout) * time.Millisecond}
|
||||
client := fhirClient.NewClient(httpClient, pageSize)
|
||||
maxTries, err := safecast.ToUint64(utils.GetEnvInt("BB_REQUEST_MAX_TRIES", 3))
|
||||
if err != nil {
|
||||
logger.Warn(errors.Wrap(err, "Could not convert Blue Button max retries from environment variable"))
|
||||
}
|
||||
retryInterval := time.Duration(utils.GetEnvInt("BB_REQUEST_RETRY_INTERVAL_MS", 1000)) * time.Millisecond
|
||||
return &BlueButtonClient{client, maxTries, retryInterval, config.BBServer, config.BBBasePath}, nil
|
||||
}
|
||||
|
||||
// SetLogger sets the logger to be used in the client.
|
||||
// Since both the API and worker use the bluebutton client, we need
|
||||
// to be able to set the appropriate logger based on who is using the client.
|
||||
func SetLogger(log logrus.FieldLogger) {
|
||||
logger = log
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetPatient(jobData worker_types.JobEnqueueArgs, patientID string) (*fhir.Bundle, error) {
|
||||
header := make(http.Header)
|
||||
header.Add("IncludeAddressFields", "true")
|
||||
params := GetDefaultParams()
|
||||
params.Set("_id", patientID)
|
||||
updateParamWithLastUpdated(¶ms, jobData.Since, jobData.TransactionTime)
|
||||
|
||||
u, err := bbc.getURL("Patient", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbc.makeBundleDataRequest("GET", u, jobData, header, nil)
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetPatientByMbi(jobData worker_types.JobEnqueueArgs, mbi string) (string, error) {
|
||||
headers := createURLEncodedHeader()
|
||||
params := GetDefaultParams()
|
||||
params.Set("identifier", fmt.Sprintf("http://hl7.org/fhir/sid/us-mbi|%s", mbi))
|
||||
|
||||
u, err := bbc.getURL("Patient/_search", url.Values{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bbc.getRawData("POST", jobData, u, headers, strings.NewReader(params.Encode()))
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetCoverage(jobData worker_types.JobEnqueueArgs, beneficiaryID string) (*fhir.Bundle, error) {
|
||||
params := GetDefaultParams()
|
||||
params.Set("beneficiary", beneficiaryID)
|
||||
updateParamWithLastUpdated(¶ms, jobData.Since, jobData.TransactionTime)
|
||||
|
||||
u, err := bbc.getURL("Coverage", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbc.makeBundleDataRequest("GET", u, jobData, nil, nil)
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetClaim(jobData worker_types.JobEnqueueArgs, mbi string, claimsWindow ClaimsWindow) (*fhir.Bundle, error) {
|
||||
headers := createURLEncodedHeader()
|
||||
params := GetDefaultParams()
|
||||
updateParamsWithClaimsDefaults(¶ms, mbi)
|
||||
updateParamWithServiceDate(¶ms, claimsWindow)
|
||||
updateParamWithLastUpdated(¶ms, jobData.Since, jobData.TransactionTime)
|
||||
|
||||
u, err := bbc.getURL("Claim/_search", url.Values{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbc.makeBundleDataRequest("POST", u, jobData, headers, strings.NewReader(params.Encode()))
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetClaimResponse(jobData worker_types.JobEnqueueArgs, mbi string, claimsWindow ClaimsWindow) (*fhir.Bundle, error) {
|
||||
headers := createURLEncodedHeader()
|
||||
params := GetDefaultParams()
|
||||
updateParamsWithClaimsDefaults(¶ms, mbi)
|
||||
updateParamWithServiceDate(¶ms, claimsWindow)
|
||||
updateParamWithLastUpdated(¶ms, jobData.Since, jobData.TransactionTime)
|
||||
|
||||
u, err := bbc.getURL("ClaimResponse/_search", url.Values{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbc.makeBundleDataRequest("POST", u, jobData, headers, strings.NewReader(params.Encode()))
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetExplanationOfBenefit(jobData worker_types.JobEnqueueArgs, patientID string, claimsWindow ClaimsWindow) (*fhir.Bundle, error) {
|
||||
header := make(http.Header)
|
||||
header.Add("IncludeTaxNumbers", "true")
|
||||
params := GetDefaultParams()
|
||||
params.Set("patient", patientID)
|
||||
|
||||
if bbc.BBBasePath != constants.BFDV3Path {
|
||||
params.Set("excludeSAMHSA", "true")
|
||||
} else {
|
||||
params.Set("_security:not", "42CFRPart2")
|
||||
}
|
||||
|
||||
updateParamWithServiceDate(¶ms, claimsWindow)
|
||||
updateParamWithLastUpdated(¶ms, jobData.Since, jobData.TransactionTime)
|
||||
updateParamWithTypeFilter(¶ms, jobData.TypeFilter)
|
||||
setRestrictiveServiceDateWindow(¶ms)
|
||||
|
||||
u, err := bbc.getURL("ExplanationOfBenefit", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bbc.makeBundleDataRequest("GET", u, jobData, header, nil)
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) GetMetadata() (string, error) {
|
||||
u, err := bbc.getURL("metadata", GetDefaultParams())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
jobData := worker_types.JobEnqueueArgs{}
|
||||
|
||||
return bbc.getRawData("GET", jobData, u, nil, nil)
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) makeBundleDataRequest(method string, u *url.URL, jobData worker_types.JobEnqueueArgs, headers http.Header, body io.Reader) (*fhir.Bundle, error) {
|
||||
var b *fhir.Bundle
|
||||
for ok := true; ok; {
|
||||
result, nextURL, err := bbc.tryBundleRequest(method, u, jobData, headers, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
b = result
|
||||
} else {
|
||||
b.Entries = append(b.Entries, result.Entries...)
|
||||
}
|
||||
|
||||
u = nextURL
|
||||
ok = nextURL != nil
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) tryBundleRequest(method string, u *url.URL, jobData worker_types.JobEnqueueArgs, headers http.Header, body io.Reader) (*fhir.Bundle, *url.URL, error) {
|
||||
var (
|
||||
result *fhir.Bundle
|
||||
nextURL *url.URL
|
||||
err error
|
||||
)
|
||||
|
||||
eb := backoff.NewExponentialBackOff()
|
||||
eb.InitialInterval = bbc.retryInterval
|
||||
b := backoff.WithMaxRetries(eb, bbc.maxTries)
|
||||
|
||||
err = backoff.RetryNotify(func() error {
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
addDefaultRequestHeaders(req, uuid.NewRandom(), jobData)
|
||||
|
||||
result, nextURL, err = bbc.client.DoBundleRequest(req)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
return err
|
||||
},
|
||||
b,
|
||||
func(err error, d time.Duration) {
|
||||
logger.Infof("Blue Button request failed %s. Retry in %s", err.Error(), d.String())
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("blue button request failed %d time(s) %s", bbc.maxTries, err.Error())
|
||||
}
|
||||
|
||||
return result, nextURL, nil
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) getRawData(method string, jobData worker_types.JobEnqueueArgs, u *url.URL, headers http.Header, body io.Reader) (string, error) {
|
||||
eb := backoff.NewExponentialBackOff()
|
||||
eb.InitialInterval = bbc.retryInterval
|
||||
b := backoff.WithMaxRetries(eb, bbc.maxTries)
|
||||
|
||||
var result string
|
||||
|
||||
err := backoff.RetryNotify(func() error {
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
addDefaultRequestHeaders(req, uuid.NewRandom(), jobData)
|
||||
|
||||
result, err = bbc.client.DoRaw(req)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
return err
|
||||
},
|
||||
b,
|
||||
func(err error, d time.Duration) {
|
||||
logger.Infof("Blue Button request failed %s. Retry in %s", err, d.String())
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("blue button request failed %d time(s) %s", bbc.maxTries, err.Error())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (bbc *BlueButtonClient) getURL(path string, params url.Values) (*url.URL, error) {
|
||||
u, err := url.Parse(fmt.Sprintf("%s%s/%s/", bbc.bbServer, bbc.BBBasePath, path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func addDefaultRequestHeaders(req *http.Request, reqID uuid.UUID, jobData worker_types.JobEnqueueArgs) {
|
||||
// Info for BB backend: https://jira.cms.gov/browse/BLUEBUTTON-483
|
||||
req.Header.Add("keep-alive", "")
|
||||
req.Header.Add(constants.BBHeaderTS, time.Now().String())
|
||||
req.Header.Add(constants.BBHeaderOriginQID, reqID.String())
|
||||
req.Header.Add(constants.BBHeaderOriginQC, "1")
|
||||
req.Header.Add(constants.BBHeaderOriginURL, req.URL.String())
|
||||
req.Header.Add("IncludeIdentifiers", "mbi")
|
||||
req.Header.Add(jobIDHeader, strconv.Itoa(jobData.ID))
|
||||
req.Header.Add(clientIDHeader, jobData.CMSID)
|
||||
req.Header.Add(TransactionIDHeader, jobData.TransactionID)
|
||||
|
||||
// We SHOULD NOT be specifying "Accept-Encoding: gzip" on the request header.
|
||||
// If we specify this header at the client level, then we must be responsible for decompressing the response.
|
||||
// This header should be automatically set by the underlying http.Transport which will handle the decompression transparently
|
||||
// Details: https://golang.org/src/net/http/transport.go#L2432
|
||||
// req.Header.Add("Accept-Encoding", "gzip")
|
||||
|
||||
// Do not set BB-specific headers with blank values
|
||||
// Leaving them here, commented out, in case we want to set them to real values in future
|
||||
//req.Header.Add("BlueButton-BeneficiaryId", "")
|
||||
//req.Header.Add("BlueButton-OriginatingIpAddress", "")
|
||||
//req.Header.Add("BlueButton-BackendCall", "")
|
||||
}
|
||||
|
||||
func GetDefaultParams() (params url.Values) {
|
||||
params = url.Values{}
|
||||
params.Set("_format", "application/fhir+json")
|
||||
return params
|
||||
}
|
||||
|
||||
// updateParamWithServiceDate adds service-date query parameters for the ClaimsWindow,
|
||||
// requesting claims on or after Earliest ("ge") and on or before Latest ("le").
|
||||
func updateParamWithServiceDate(params *url.Values, claimsWindow ClaimsWindow) {
|
||||
// ServiceDate only uses yyyy-mm-dd
|
||||
const isoDate = "2006-01-02"
|
||||
|
||||
if !claimsWindow.Earliest.IsZero() {
|
||||
params.Add("service-date", fmt.Sprintf("ge%s", claimsWindow.Earliest.Format(isoDate)))
|
||||
}
|
||||
|
||||
if !claimsWindow.Latest.IsZero() {
|
||||
params.Add("service-date", fmt.Sprintf("le%s", claimsWindow.Latest.Format(isoDate)))
|
||||
}
|
||||
}
|
||||
|
||||
type serviceDateVal struct {
|
||||
prefix string
|
||||
date time.Time
|
||||
}
|
||||
|
||||
// setRestrictiveServiceDateWindow sets the most restrictive window of time from which to pull data from BFD via service-date params.
|
||||
// BFD only allows for one earliest boundary and one latest boundary.
|
||||
// We need to do some comparisons to make sure we apply the most restrictive option for each.
|
||||
// eg "gt2025" is a more restrictive earliest boundary than "gt2024" as that will only get us data starting from 2025 and not all the way back to 2024.
|
||||
// known edge cases that are currently not accounted for (low risk, low priority, time constraints):
|
||||
// - we dont allow for hours, mins, secs, tz, etc
|
||||
func setRestrictiveServiceDateWindow(params *url.Values) {
|
||||
serviceDates := (*params)["service-date"]
|
||||
if len(serviceDates) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var earliestDates, latestDates []serviceDateVal
|
||||
for _, date := range serviceDates {
|
||||
if strings.HasPrefix(date, "ge") || strings.HasPrefix(date, "gt") {
|
||||
parsed, err := fhir.ParseDateString(date[2:])
|
||||
if err == nil {
|
||||
earliestDates = append(earliestDates, serviceDateVal{prefix: date[:2], date: parsed})
|
||||
}
|
||||
} else if strings.HasPrefix(date, "le") || strings.HasPrefix(date, "lt") {
|
||||
parsed, err := fhir.ParseDateString(date[2:])
|
||||
if err == nil {
|
||||
latestDates = append(latestDates, serviceDateVal{prefix: date[:2], date: parsed})
|
||||
}
|
||||
} else if strings.HasPrefix(date, "eq") {
|
||||
parsed, err := fhir.ParseDateString(date[2:])
|
||||
if err == nil {
|
||||
earliestDates = append(earliestDates, serviceDateVal{prefix: "ge", date: parsed})
|
||||
latestDates = append(latestDates, serviceDateVal{prefix: "lt", date: parseLatestDateFromEqualPrefix(date[2:])})
|
||||
}
|
||||
} else if strings.HasPrefix(date, "20") {
|
||||
parsed, err := fhir.ParseDateString(date)
|
||||
if err == nil {
|
||||
earliestDates = append(earliestDates, serviceDateVal{prefix: "ge", date: parsed})
|
||||
latestDates = append(latestDates, serviceDateVal{prefix: "lt", date: parseLatestDateFromEqualPrefix(date)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any zero dates to cleanse list of invalids
|
||||
earliestDates = slices.DeleteFunc(earliestDates, func(s serviceDateVal) bool { return s.date.IsZero() })
|
||||
latestDates = slices.DeleteFunc(latestDates, func(s serviceDateVal) bool { return s.date.IsZero() })
|
||||
|
||||
// sort earliest in descending order, and latest in ascending order so we can pick the most restrictive for each
|
||||
slices.SortFunc(earliestDates, func(a, b serviceDateVal) int {
|
||||
if c := b.date.Compare(a.date); c != 0 {
|
||||
return c
|
||||
}
|
||||
|
||||
if a.prefix == "gt" {
|
||||
return -1
|
||||
}
|
||||
if b.prefix == "gt" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
slices.SortFunc(latestDates, func(a, b serviceDateVal) int {
|
||||
if c := a.date.Compare(b.date); c != 0 {
|
||||
return c
|
||||
}
|
||||
|
||||
if a.prefix == "lt" {
|
||||
return -1
|
||||
}
|
||||
if b.prefix == "lt" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
// remove all existing service-date instances so we can start from scratch
|
||||
params.Del("service-date")
|
||||
if len(earliestDates) > 0 {
|
||||
params.Add("service-date", earliestDates[0].prefix+earliestDates[0].date.Format("2006-01-02"))
|
||||
}
|
||||
if len(latestDates) > 0 {
|
||||
params.Add("service-date", latestDates[0].prefix+latestDates[0].date.Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
|
||||
func parseLatestDateFromEqualPrefix(date string) time.Time {
|
||||
// date interval of a day; latest date is the beginning of the next day
|
||||
parsedDate, err := time.Parse("2006-01-02", date)
|
||||
if err == nil {
|
||||
return parsedDate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
// date interval of a month; latest date is the beginning of first day of the next month
|
||||
parsedDate, err = time.Parse("2006-01", date)
|
||||
if err == nil {
|
||||
return parsedDate.AddDate(0, 1, 0)
|
||||
}
|
||||
|
||||
// date interval of a year; latest date is the beginning of the first day of the next year
|
||||
parsedDate, err = time.Parse("2006", date)
|
||||
if err == nil {
|
||||
return parsedDate.AddDate(1, 0, 0)
|
||||
}
|
||||
|
||||
// if a time is specified, treat it like a day interval; latest date is the beginning of the next day
|
||||
// ToDo: update this and upstream/downstream logic to properly handle times
|
||||
parsedDate, err = fhir.ParseDateString(date)
|
||||
if err == nil {
|
||||
return parsedDate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func updateParamWithLastUpdated(params *url.Values, since string, transactionTime time.Time) {
|
||||
// upper bound will always be set
|
||||
params.Set("_lastUpdated", "le"+transactionTime.Format(time.RFC3339Nano))
|
||||
|
||||
// only set the lower bound parameter if it exists and begins with "gt" (to align with what is expected in _lastUpdated)
|
||||
if len(since) > 0 && strings.HasPrefix(since, "gt") {
|
||||
params.Add("_lastUpdated", since)
|
||||
}
|
||||
}
|
||||
|
||||
func updateParamWithTypeFilter(params *url.Values, typeFilter fhir.TypeFilterSubquery) {
|
||||
for _, subqueryParam := range typeFilter.QueryParameters {
|
||||
params.Add(subqueryParam.Name, subqueryParam.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func updateParamsWithClaimsDefaults(params *url.Values, mbi string) {
|
||||
params.Set("excludeSAMHSA", "true")
|
||||
params.Set("includeTaxNumbers", "true")
|
||||
params.Set("isHashed", "false")
|
||||
params.Set("mbi", mbi)
|
||||
}
|
||||
|
||||
func createURLEncodedHeader() http.Header {
|
||||
headers := make(http.Header)
|
||||
headers.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
type httpLogger struct {
|
||||
t *http.Transport
|
||||
l logrus.FieldLogger
|
||||
}
|
||||
|
||||
func (h *httpLogger) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
go h.logRequest(req.Clone(context.Background()))
|
||||
resp, err := h.t.RoundTrip(req)
|
||||
if resp != nil {
|
||||
h.logResponse(req, resp)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *httpLogger) logRequest(req *http.Request) {
|
||||
h.l.WithFields(logrus.Fields{
|
||||
"bb_query_id": req.Header.Get(constants.BBHeaderOriginQID),
|
||||
"bb_query_ts": req.Header.Get(constants.BBHeaderTS),
|
||||
"bb_uri": req.URL.String(),
|
||||
"job_id": req.Header.Get(jobIDHeader),
|
||||
"cms_id": req.Header.Get(clientIDHeader),
|
||||
}).Infoln("request")
|
||||
}
|
||||
|
||||
func (h *httpLogger) logResponse(req *http.Request, resp *http.Response) {
|
||||
h.l.WithFields(logrus.Fields{
|
||||
"resp_status": resp.StatusCode,
|
||||
"bb_query_id": req.Header.Get(constants.BBHeaderOriginQID),
|
||||
"bb_query_ts": req.Header.Get(constants.BBHeaderTS),
|
||||
"bb_uri": req.URL.String(),
|
||||
"job_id": req.Header.Get(jobIDHeader),
|
||||
"cms_id": req.Header.Get(clientIDHeader),
|
||||
"transaction_id": req.Header.Get(TransactionIDHeader),
|
||||
}).Infoln("response")
|
||||
}
|
||||
68
dev/vendor/bcda-app/bcda/constants/constants.go
vendored
Normal file
68
dev/vendor/bcda-app/bcda/constants/constants.go
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package constants
|
||||
|
||||
const DevACOUUID = "0c527d2e-2e8a-4808-b11d-0fa06baf8254"
|
||||
const SmallACOUUID = "3461C774-B48F-11E8-96F8-529269fb1459"
|
||||
const MediumACOUUID = "C74C008D-42F8-4ED9-BF88-CEE659C7F692"
|
||||
const LargeACOUUID = "8D80925A-027E-43DD-8AED-9A501CC4CD91"
|
||||
|
||||
const ImportInprog = "In-Progress"
|
||||
const ImportComplete = "Completed"
|
||||
const ImportFail = "Failed"
|
||||
|
||||
// This is set during compilation. See dockerfiles for usage
|
||||
var Version = "latest"
|
||||
|
||||
const Adjudicated = "adjudicated"
|
||||
const PartiallyAdjudicated = "partially-adjudicated"
|
||||
|
||||
// Cli.go constants
|
||||
const CliCMSIDArg = "cms-id"
|
||||
const CliCMSIDDesc = "CMS ID of ACO"
|
||||
const CliArchDesc = "How long files should wait in archive before deletion"
|
||||
const CliRemoveArchDesc = "Remove job directory and files from archive and update job status to Expired"
|
||||
const CliAuthToolsCategory = "Authentication tools"
|
||||
const CliDataImpCategory = "Data import"
|
||||
|
||||
const ContentType = "Content-Type"
|
||||
const JsonContentType = "application/json"
|
||||
const FHIRJsonContentType = "application/fhir+json"
|
||||
|
||||
const BBHeaderTS = "BlueButton-OriginalQueryTimestamp"
|
||||
const BBHeaderOriginURL = "BlueButton-OriginalUrl"
|
||||
const BBHeaderOriginQID = "BlueButton-OriginalQueryId"
|
||||
const BBHeaderOriginQ = "BlueButton-OriginalQuery"
|
||||
const BBHeaderOriginQC = "BlueButton-OriginalQueryCounter"
|
||||
|
||||
const CCLFFileRetID = "%s RETURNING id"
|
||||
const JobKeyCreateErr = "failed to create job key: %w"
|
||||
|
||||
const JOBIDPath = "/jobs/{jobID}"
|
||||
|
||||
const IssuerSSAS = "ssas"
|
||||
|
||||
const EmptyString = ""
|
||||
|
||||
const FiveSeconds = "5"
|
||||
const FiveHundredSeconds = "500"
|
||||
|
||||
const CCLF8FileNum = int(8)
|
||||
|
||||
const BFDV1Path = "/v1/fhir"
|
||||
const BFDV2Path = "/v2/fhir"
|
||||
const BFDV3Path = "/v3/fhir"
|
||||
const V3Version = "v3"
|
||||
|
||||
const GetExistingBenes = "GetExistingBenes"
|
||||
const GetNewAndExistingBenes = "GetNewAndExistingBenes"
|
||||
|
||||
const DefaultRegion = "us-east-1"
|
||||
|
||||
const PublisherName = "Centers for Medicare & Medicaid Services"
|
||||
const SoftwareName = "Beneficiary Claims Data API"
|
||||
const SoftwareDescription = "The Beneficiary Claims Data API (BCDA) enables healthcare organizations participating in the Medicare Shared Savings Program or a CMS Innovation Model to retrieve Medicare Part A, Part B, and Part D claims data for their prospectively assigned or assignable beneficiaries."
|
||||
|
||||
const SmartOAuthURIsExtensionURL = "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
|
||||
const RestfulSecurityServiceSystem = "http://terminology.hl7.org/CodeSystem/restful-security-service"
|
||||
const BFDSystemTypeURL = "https://bluebutton.cms.gov/fhir/CodeSystem/System-Type"
|
||||
const BFDFinalActionURL = "https://bluebutton.cms.gov/fhir/CodeSystem/Final-Action"
|
||||
const WarningsAndInfoFileName = "warnings-and-info.ndjson"
|
||||
9
dev/vendor/bcda-app/bcda/constants/types.go
vendored
Normal file
9
dev/vendor/bcda-app/bcda/constants/types.go
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package constants
|
||||
|
||||
type DataRequestType uint8
|
||||
|
||||
const (
|
||||
DefaultRequest DataRequestType = iota
|
||||
RetrieveNewBeneHistData // Allows caller to retrieve all of the data for newly attributed beneficiaries
|
||||
Runout // Allows caller to retrieve claims data for beneficiaries no longer attributed to the ACO
|
||||
)
|
||||
304
dev/vendor/bcda-app/bcda/fhir/param.go
vendored
Normal file
304
dev/vendor/bcda-app/bcda/fhir/param.go
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
package fhir
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/CMSgov/bcda-app/bcda/constants"
|
||||
)
|
||||
|
||||
type TypeFilterSubquery struct {
|
||||
ResourceType string
|
||||
QueryParameters []TypeFilterSubqueryParam
|
||||
}
|
||||
|
||||
type TypeFilterSubqueryParam struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type TypeFilterParamName string
|
||||
|
||||
const (
|
||||
TypeFilterParamOutcome TypeFilterParamName = "outcome"
|
||||
TypeFilterParamServiceDate TypeFilterParamName = "service-date"
|
||||
TypeFilterParamTag TypeFilterParamName = "_tag"
|
||||
)
|
||||
|
||||
type ParameterParsingError struct {
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e ParameterParsingError) Error() string {
|
||||
return fmt.Sprintf("malformed parameter: %s", e.Details)
|
||||
}
|
||||
|
||||
type ParameterValidationError struct {
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e ParameterValidationError) Error() string {
|
||||
return fmt.Sprintf("invalid parameter: %s", e.Details)
|
||||
}
|
||||
|
||||
func ParseTypeFilterSubquery(s string) (TypeFilterSubquery, error) {
|
||||
// Expected format is: <resourceType>?<paramList>
|
||||
resourceType, params, found := strings.Cut(s, "?")
|
||||
if !found {
|
||||
return TypeFilterSubquery{}, ParameterParsingError{Details: fmt.Sprintf("_typeFilter parameter missing question mark %s", s)}
|
||||
} else if len(resourceType) == 0 {
|
||||
return TypeFilterSubquery{}, ParameterParsingError{Details: fmt.Sprintf("_typeFilter parameter missing resource type %s", s)}
|
||||
} else if len(params) == 0 {
|
||||
return TypeFilterSubquery{}, ParameterParsingError{Details: fmt.Sprintf("_typeFilter parameter missing value %s", s)}
|
||||
}
|
||||
|
||||
var subqueryParams []TypeFilterSubqueryParam
|
||||
paramAry := strings.SplitSeq(params, "&")
|
||||
for paramPair := range paramAry {
|
||||
name, value, found := strings.Cut(paramPair, "=")
|
||||
if !found {
|
||||
return TypeFilterSubquery{}, ParameterParsingError{Details: fmt.Sprintf("_typeFilter value missing equals sign: %s", paramPair)}
|
||||
}
|
||||
subqueryParams = append(subqueryParams, TypeFilterSubqueryParam{Name: name, Value: value})
|
||||
}
|
||||
return TypeFilterSubquery{ResourceType: resourceType, QueryParameters: subqueryParams}, nil
|
||||
}
|
||||
|
||||
func ValidateTypeFilterSubquery(subquery TypeFilterSubquery) error {
|
||||
if subquery.ResourceType != "ExplanationOfBenefit" {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid _typeFilter Resource Type (Only EOBs valid): %s", subquery.ResourceType)}
|
||||
}
|
||||
|
||||
var serviceDateParams []DateParam
|
||||
for _, param := range subquery.QueryParameters {
|
||||
if strings.HasPrefix(param.Name, string(TypeFilterParamTag)) {
|
||||
tag, err := ParseTokenParam(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ValidateTag(tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasPrefix(param.Name, string(TypeFilterParamServiceDate)) {
|
||||
date, err := ParseDateParam(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceDateParams = append(serviceDateParams, date)
|
||||
} else if strings.HasPrefix(param.Name, string(TypeFilterParamOutcome)) {
|
||||
outcome, err := ParseTokenParam(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ValidateOutcome(outcome)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid _typeFilter subquery parameter: %s", param.Name)}
|
||||
}
|
||||
}
|
||||
err := ValidateServiceDates(serviceDateParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTagParams(subquery TypeFilterSubquery) ([]TokenParam, error) {
|
||||
tagParams := []TokenParam{}
|
||||
for _, param := range subquery.QueryParameters {
|
||||
if strings.HasPrefix(param.Name, string(TypeFilterParamTag)) {
|
||||
tag, err := ParseTokenParam(param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagParams = append(tagParams, tag)
|
||||
}
|
||||
}
|
||||
return tagParams, nil
|
||||
}
|
||||
|
||||
// https://hl7.org/fhir/R4/search.html#string
|
||||
type StringParam struct {
|
||||
Name string
|
||||
Modifier string
|
||||
Values []string
|
||||
}
|
||||
|
||||
func ParseStringParam(subqueryParam TypeFilterSubqueryParam) (StringParam, error) {
|
||||
s := StringParam{}
|
||||
s.Name, s.Modifier, _ = strings.Cut(subqueryParam.Name, ":")
|
||||
if len(s.Name) == 0 {
|
||||
return s, ParameterParsingError{Details: "string parameter missing name"}
|
||||
}
|
||||
if len(subqueryParam.Value) == 0 {
|
||||
return s, ParameterParsingError{Details: fmt.Sprintf("string parameter missing value: %s", s.Name)}
|
||||
}
|
||||
s.Values = strings.Split(subqueryParam.Value, ",")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// https://hl7.org/fhir/R4/search.html#date
|
||||
type DateParam struct {
|
||||
Name string
|
||||
Prefix string
|
||||
Datetimes []string
|
||||
}
|
||||
|
||||
func ParseDateParam(subqueryParam TypeFilterSubqueryParam) (DateParam, error) {
|
||||
d := DateParam{}
|
||||
if len(subqueryParam.Name) == 0 {
|
||||
return d, ParameterParsingError{Details: "date parameter missing key"}
|
||||
}
|
||||
d.Name = subqueryParam.Name
|
||||
|
||||
if len(subqueryParam.Value) == 0 {
|
||||
return d, ParameterParsingError{Details: fmt.Sprintf("date parameter missing value: %s", d.Name)}
|
||||
}
|
||||
|
||||
// BFD only supports eq, ge, gt, lt, le as of 2026-08-17. See: https://cmsgov.slack.com/archives/CMT1YS2KY/p1786716165942379
|
||||
var prefixes = []string{"eq", "lt", "gt", "le", "ge"} // "ne", "sa", "eb", "ap"}
|
||||
afterPrefix := ""
|
||||
if len(subqueryParam.Value) >= 2 && slices.Contains(prefixes, subqueryParam.Value[:2]) {
|
||||
d.Prefix = subqueryParam.Value[:2]
|
||||
afterPrefix = subqueryParam.Value[2:]
|
||||
} else {
|
||||
afterPrefix = subqueryParam.Value
|
||||
}
|
||||
|
||||
if len(afterPrefix) == 0 {
|
||||
return d, ParameterParsingError{Details: fmt.Sprintf("value must include a valid FHIR date for date parameter %s", d.Name)}
|
||||
}
|
||||
|
||||
d.Datetimes = strings.Split(afterPrefix, ",")
|
||||
|
||||
for _, datetime := range d.Datetimes {
|
||||
_, err := ParseDateString(datetime)
|
||||
if err != nil {
|
||||
return d, ParameterParsingError{Details: fmt.Sprintf("invalid date parameter value: %s", datetime)}
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func ParseDateString(datetime string) (time.Time, error) {
|
||||
var datetimeFormats = []string{
|
||||
"2006",
|
||||
"2006-01",
|
||||
"2006-01-02",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02T15:04:05+07:00",
|
||||
"2006-01-02T15:04:05-07:00",
|
||||
"2006-01-02T15:04:05Z",
|
||||
}
|
||||
for _, format := range datetimeFormats {
|
||||
if parsed, err := time.Parse(format, datetime); err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, ParameterParsingError{Details: fmt.Sprintf("invalid date parameter value: %s", datetime)}
|
||||
}
|
||||
|
||||
// ValidateServiceDates ensures that service-date parameters have valid keys, non-delimited values,
|
||||
// and at most one earliest ("gt"/"ge") and one latest ("lt"/"le") condition.
|
||||
// Exact date conditions ("eq" or "") establish both an earliest and a latest boundary.
|
||||
func ValidateServiceDates(serviceDateParams []DateParam) error {
|
||||
earliestBoundCount, latestBoundCount := 0, 0
|
||||
for _, sd := range serviceDateParams {
|
||||
if sd.Name != string(TypeFilterParamServiceDate) {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid key for service-date parameter: %s", sd.Name)}
|
||||
}
|
||||
if len(sd.Datetimes) > 1 {
|
||||
return ParameterValidationError{Details: "invalid service-date parameter value: comma-separated values are not supported"}
|
||||
}
|
||||
switch sd.Prefix {
|
||||
case "lt", "le":
|
||||
latestBoundCount = latestBoundCount + 1
|
||||
case "gt", "ge":
|
||||
earliestBoundCount = earliestBoundCount + 1
|
||||
case "eq", "":
|
||||
latestBoundCount = latestBoundCount + 1
|
||||
earliestBoundCount = earliestBoundCount + 1
|
||||
}
|
||||
}
|
||||
if earliestBoundCount > 1 || latestBoundCount > 1 {
|
||||
return ParameterValidationError{Details: "invalid service-date parameter value: conflicting prefix conditions"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://hl7.org/fhir/R4/search.html#token
|
||||
type TokenParam struct {
|
||||
Name string
|
||||
Modifier string
|
||||
Values []TokenValue
|
||||
}
|
||||
|
||||
type TokenValue struct {
|
||||
System string
|
||||
Code string
|
||||
}
|
||||
|
||||
func ParseTokenParam(subqueryParam TypeFilterSubqueryParam) (TokenParam, error) {
|
||||
t := TokenParam{}
|
||||
if len(subqueryParam.Name) == 0 {
|
||||
return t, ParameterParsingError{Details: "token parameter missing key"}
|
||||
}
|
||||
t.Name, t.Modifier, _ = strings.Cut(subqueryParam.Name, ":")
|
||||
if len(subqueryParam.Value) == 0 {
|
||||
return t, ParameterParsingError{Details: fmt.Sprintf("token parameter missing value %s", t.Name)}
|
||||
}
|
||||
values := strings.SplitSeq(subqueryParam.Value, ",")
|
||||
for value := range values {
|
||||
before, after, found := strings.Cut(value, "|")
|
||||
if found {
|
||||
t.Values = append(t.Values, TokenValue{System: before, Code: after})
|
||||
} else {
|
||||
t.Values = append(t.Values, TokenValue{Code: before})
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
var validTagTokens = map[string][]string{
|
||||
constants.BFDSystemTypeURL: {"SharedSystem", "NationalClaimsHistory", "DDPS"},
|
||||
constants.BFDFinalActionURL: {"FinalAction", "NotFinalAction"},
|
||||
}
|
||||
|
||||
func ValidateTag(t TokenParam) error {
|
||||
if t.Name != string(TypeFilterParamTag) {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid key for _tag parameter: %s", t.Name)}
|
||||
}
|
||||
if len(t.Modifier) > 0 {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid _tag parameter; modifier %s not supported", t.Modifier)}
|
||||
}
|
||||
for _, value := range t.Values {
|
||||
if len(value.System) == 0 || len(value.Code) == 0 {
|
||||
return ParameterValidationError{Details: "invalid _tag parameter value. Searching by _tag requires a token (system|code) to be specified"}
|
||||
}
|
||||
validTagCodes, ok := validTagTokens[value.System]
|
||||
if !ok || !slices.Contains(validTagCodes, value.Code) {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid _tag parameter, unsupported code %s for system %s", value.Code, value.System)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateOutcome(t TokenParam) error {
|
||||
if t.Name != string(TypeFilterParamOutcome) {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid key for outcome parameter: %s", t.Name)}
|
||||
}
|
||||
if len(t.Modifier) > 0 {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid outcome parameter; modifier %s not supported", t.Modifier)}
|
||||
}
|
||||
for _, value := range t.Values {
|
||||
if value.Code != "complete" && value.Code != "partial" {
|
||||
return ParameterValidationError{Details: fmt.Sprintf("invalid outcome code: %s. Supported outcome codes are 'complete' and 'partial'", value.Code)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
174
dev/vendor/bcda-app/bcda/models/models.go
vendored
Normal file
174
dev/vendor/bcda-app/bcda/models/models.go
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
JobStatusArchived JobStatus = "Archived"
|
||||
JobStatusCancelled JobStatus = "Cancelled"
|
||||
JobStatusCancelledExpired JobStatus = "CancelledExpired" // JobStatusCancelledExpired represents a job that has been cancelled whose data has been cleaned up
|
||||
JobStatusCompleted JobStatus = "Completed"
|
||||
JobStatusExpired JobStatus = "Expired"
|
||||
JobStatusFailed JobStatus = "Failed"
|
||||
JobStatusFailedExpired JobStatus = "FailedExpired" // JobStatusFailedExpired represents a job that failed whose data has been cleaned up
|
||||
JobStatusInProgress JobStatus = "In Progress"
|
||||
JobStatusPending JobStatus = "Pending"
|
||||
)
|
||||
|
||||
var AllJobStatuses = []JobStatus{
|
||||
JobStatusArchived,
|
||||
JobStatusCancelled,
|
||||
JobStatusCancelledExpired,
|
||||
JobStatusCompleted,
|
||||
JobStatusExpired,
|
||||
JobStatusFailed,
|
||||
JobStatusFailedExpired,
|
||||
JobStatusInProgress,
|
||||
JobStatusPending,
|
||||
}
|
||||
|
||||
type JobStatus string
|
||||
type Job struct {
|
||||
ID uint
|
||||
ACOID uuid.UUID `json:"aco_id"`
|
||||
RequestURL string `json:"request_url"` // request_url
|
||||
Status JobStatus `json:"status"` // status
|
||||
TransactionTime time.Time // most recent data load transaction time from BFD
|
||||
JobCount int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
BenesAttributedToACO int // Total beneficiaries attributed to ACO at time of job request
|
||||
}
|
||||
|
||||
func (j *Job) StatusMessage(numCompletedJobKeys int) string {
|
||||
if j.Status == JobStatusInProgress && j.JobCount > 0 {
|
||||
pct := float64(numCompletedJobKeys) / float64(j.JobCount) * 100
|
||||
return fmt.Sprintf("%s (%d%%)", j.Status, int(pct))
|
||||
}
|
||||
|
||||
return string(j.Status)
|
||||
}
|
||||
|
||||
// BlankFileName contains the naming convention for empty ndjson file
|
||||
const BlankFileName string = "blank.ndjson"
|
||||
|
||||
type JobKey struct {
|
||||
ID uint
|
||||
JobID uint `json:"job_id"`
|
||||
// Although que_job records are temporary, we store the ID to ensure
|
||||
// that workers are never duplicating job keys.
|
||||
QueJobID *int64
|
||||
FileName string
|
||||
ResourceType string
|
||||
BenesWithData int // count of beneficiaries with entry data
|
||||
BenesRetrievedPercent int // percent of beneficiaries successfully retrieved from BFD
|
||||
}
|
||||
|
||||
func (j *JobKey) IsError() bool {
|
||||
return strings.Contains(j.FileName, "-error.ndjson")
|
||||
}
|
||||
|
||||
// ACO represents an Accountable Care Organization.
|
||||
type ACO struct {
|
||||
ID uint
|
||||
UUID uuid.UUID `json:"uuid"`
|
||||
CMSID *string `json:"cms_id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
SystemID string `json:"system_id"`
|
||||
TerminationDetails *Termination `json:"termination"`
|
||||
}
|
||||
|
||||
// Denylisted returns bool based on TerminationDetails.
|
||||
// If CutoffDate is specified, the ACO is denylisted when time.Now() >= CutoffDate.
|
||||
// If CutoffDate is not specified, it falls back to checking DenylistType.
|
||||
func (aco *ACO) Denylisted() bool {
|
||||
if aco.TerminationDetails != nil {
|
||||
if !aco.TerminationDetails.CutoffDate.IsZero() {
|
||||
return !time.Now().Before(aco.TerminationDetails.CutoffDate)
|
||||
}
|
||||
return aco.TerminationDetails.DenylistType == Involuntary || aco.TerminationDetails.DenylistType == Voluntary
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type CCLFFileType int16
|
||||
|
||||
const (
|
||||
FileTypeDefault CCLFFileType = iota
|
||||
FileTypeRunout
|
||||
)
|
||||
|
||||
// String returns the letter associated with the CCLFFileType.
|
||||
func (t CCLFFileType) String() string {
|
||||
return [...]string{"Y", "R"}[t]
|
||||
}
|
||||
|
||||
type CCLFFile struct {
|
||||
ID uint
|
||||
CCLFNum int
|
||||
Name string
|
||||
ACOCMSID string
|
||||
Timestamp time.Time
|
||||
PerformanceYear int
|
||||
ImportStatus string
|
||||
Type CCLFFileType
|
||||
// CreatedAt is automatically set by the database. This is only
|
||||
// set/pulled when querying data in the DB.
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// "The MBI has 11 characters, like the Health Insurance Claim Number (HICN), which can have up to 11."
|
||||
// https://www.cms.gov/Medicare/New-Medicare-Card/Understanding-the-MBI-with-Format.pdf
|
||||
type CCLFBeneficiary struct {
|
||||
ID uint
|
||||
FileID uint
|
||||
MBI string
|
||||
BlueButtonID string
|
||||
}
|
||||
|
||||
// A BenePrefsFile is a basic file representation that can be stored in a database.
|
||||
type BenePrefsFile struct {
|
||||
ID uint
|
||||
Name string
|
||||
Timestamp time.Time
|
||||
ImportStatus string
|
||||
}
|
||||
|
||||
// BenePrefsFilenameMetadata is metadata information parsed from the filename.
|
||||
type BenePrefsFilenameMetadata struct {
|
||||
Name string
|
||||
Timestamp time.Time
|
||||
FilePath string
|
||||
Imported bool
|
||||
DeliveryDate time.Time
|
||||
FileID uint
|
||||
}
|
||||
|
||||
func (m BenePrefsFilenameMetadata) String() string {
|
||||
if m.FilePath != "" {
|
||||
return m.FilePath
|
||||
}
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// A BenePrefsRecord represents a single record parsed from an bene-prefs file.
|
||||
type BenePrefsRecord struct {
|
||||
ID uint
|
||||
FileID uint
|
||||
MBI string
|
||||
SourceCode string
|
||||
EffectiveDt time.Time
|
||||
PrefIndicator string
|
||||
SAMHSASourceCode string
|
||||
SAMHSAEffectiveDt time.Time
|
||||
SAMHSAPrefIndicator string
|
||||
ACOCMSID string
|
||||
BeneficiaryLinkKey int
|
||||
}
|
||||
710
dev/vendor/bcda-app/bcda/service/service.go
vendored
Normal file
710
dev/vendor/bcda-app/bcda/service/service.go
vendored
Normal file
@@ -0,0 +1,710 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ccoveille/go-safecast"
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/CMSgov/bcda-app/bcda/constants"
|
||||
bcdaerrors "github.com/CMSgov/bcda-app/bcda/errors"
|
||||
"github.com/CMSgov/bcda-app/bcda/models"
|
||||
"github.com/CMSgov/bcda-app/bcda/utils"
|
||||
"github.com/CMSgov/bcda-app/bcdaworker/queueing/worker_types"
|
||||
"github.com/CMSgov/bcda-app/conf"
|
||||
"github.com/CMSgov/bcda-app/log"
|
||||
"github.com/CMSgov/bcda-app/middleware"
|
||||
)
|
||||
|
||||
// Ensure service satisfies the interface
|
||||
var _ Service = &service{}
|
||||
|
||||
// Service contains all of the methods needed to interact with the data represented in the models package
|
||||
type Service interface {
|
||||
GetCutoffTime(ctx context.Context, reqType constants.DataRequestType, since time.Time, timeConstraints TimeConstraints, fileType models.CCLFFileType) (time.Time, string)
|
||||
FindOldCCLFFile(ctx context.Context, cmsID string, since time.Time, cclfTimestamp time.Time) (uint, error)
|
||||
GetQueJobs(ctx context.Context, args worker_types.PrepareJobArgs) (queJobs []*worker_types.JobEnqueueArgs, benesAttributed int, err error)
|
||||
GetJobAndKeys(ctx context.Context, jobID uint) (*models.Job, []*models.JobKey, error)
|
||||
GetJobKey(ctx context.Context, jobID uint, filename string) (*models.JobKey, error)
|
||||
GetJobs(ctx context.Context, acoID uuid.UUID, statuses ...models.JobStatus) ([]*models.Job, error)
|
||||
CancelJob(ctx context.Context, jobID uint) (uint, error)
|
||||
GetJobPriority(acoID string, resourceType string, sinceParam bool) int16
|
||||
GetLatestCCLFFile(ctx context.Context, cmsID string, earlierTime time.Time, laterTime time.Time, fileType models.CCLFFileType) (*models.CCLFFile, error)
|
||||
GetACOConfigForID(cmsID string) (*ACOConfig, bool)
|
||||
GetTimeConstraints(ctx context.Context, cmsID string) (TimeConstraints, error)
|
||||
GetAttributionStatusDates(ctx context.Context, cmsID string, timeConstraints TimeConstraints, fileType models.CCLFFileType) (lastUpdated time.Time, expirationDate time.Time, err error)
|
||||
IsV3NoPartialClaimsModel(model string) bool
|
||||
}
|
||||
|
||||
type service struct {
|
||||
repository models.Repository
|
||||
|
||||
logger logrus.FieldLogger
|
||||
|
||||
stdCutoffDuration time.Duration
|
||||
sp suppressionParameters
|
||||
rp runoutParameters
|
||||
bbBasePath string
|
||||
|
||||
// These are always searched in order and first matching config is used for any given ACO.
|
||||
acoConfigs []ACOConfig
|
||||
v3NoPartialClaimsModels []string
|
||||
}
|
||||
|
||||
func NewService(r models.Repository, cfg *Config, basePath string) Service {
|
||||
return &service{
|
||||
repository: r,
|
||||
logger: log.API,
|
||||
stdCutoffDuration: cfg.CutoffDuration,
|
||||
sp: suppressionParameters{
|
||||
includeSuppressedBeneficiaries: false,
|
||||
lookbackDays: cfg.SuppressionLookbackDays,
|
||||
},
|
||||
rp: runoutParameters{
|
||||
// Runouts apply to claims data for the previous year.
|
||||
claimThruDate: cfg.RunoutConfig.claimThru,
|
||||
CutoffDuration: cfg.RunoutConfig.CutoffDuration,
|
||||
},
|
||||
bbBasePath: basePath,
|
||||
acoConfigs: cfg.ACOConfigs,
|
||||
v3NoPartialClaimsModels: cfg.V3NoPartialClaimsModels,
|
||||
}
|
||||
}
|
||||
|
||||
type TimeConstraints struct {
|
||||
AttributionDate time.Time
|
||||
OptOutDate time.Time
|
||||
ClaimsDate time.Time
|
||||
}
|
||||
|
||||
type suppressionParameters struct {
|
||||
includeSuppressedBeneficiaries bool
|
||||
lookbackDays int
|
||||
}
|
||||
|
||||
type runoutParameters struct {
|
||||
// All claims data occur at or before this date
|
||||
claimThruDate time.Time
|
||||
// Amount of time the callers can retrieve runout data (relative to when runout data was ingested)
|
||||
CutoffDuration time.Duration
|
||||
}
|
||||
|
||||
func (s *service) GetCutoffTime(ctx context.Context, reqType constants.DataRequestType, since time.Time, timeConstraints TimeConstraints, fileType models.CCLFFileType) (cutoffTime time.Time, complexDataRequestType string) {
|
||||
hasAttributionDate := !timeConstraints.AttributionDate.IsZero()
|
||||
// for default requests, runouts, or any requests where the Since parameter is
|
||||
// after a terminated ACO's attribution date, we should only retrieve exisiting benes
|
||||
if reqType == constants.DefaultRequest ||
|
||||
reqType == constants.Runout ||
|
||||
(hasAttributionDate && since.After(timeConstraints.AttributionDate)) {
|
||||
complexDataRequestType = constants.GetExistingBenes
|
||||
// only set a cutoffTime if there is no attributionDate set
|
||||
if timeConstraints.AttributionDate.IsZero() {
|
||||
if fileType == models.FileTypeDefault && s.stdCutoffDuration > 0 {
|
||||
cutoffTime = time.Now().Add(-1 * s.stdCutoffDuration)
|
||||
} else if fileType == models.FileTypeRunout && s.rp.CutoffDuration > 0 {
|
||||
cutoffTime = time.Now().Add(-1 * s.rp.CutoffDuration)
|
||||
}
|
||||
}
|
||||
} else if reqType == constants.RetrieveNewBeneHistData {
|
||||
complexDataRequestType = constants.GetNewAndExistingBenes
|
||||
// only set cutoffTime if there is no attributionDate set
|
||||
if s.stdCutoffDuration > 0 && timeConstraints.AttributionDate.IsZero() {
|
||||
cutoffTime = time.Now().Add(-1 * s.stdCutoffDuration)
|
||||
}
|
||||
}
|
||||
|
||||
return cutoffTime, complexDataRequestType
|
||||
}
|
||||
|
||||
// GetAttributionStatusDates provides the last updated date and the final date for which the most recent CCLF file for a CMS ID
|
||||
// can be used to provide attribution information for data requests of a given file type.
|
||||
//
|
||||
// The expiration date can be limited by the date of the most recent CCLF file or the termination of the model entity in question.
|
||||
func (s *service) GetAttributionStatusDates(ctx context.Context, cmsID string, timeConstraints TimeConstraints, fileType models.CCLFFileType) (lastUpdated time.Time, expirationDate time.Time, err error) {
|
||||
latestFile, err := s.GetLatestCCLFFile(ctx, cmsID, expirationDate, expirationDate, fileType)
|
||||
if err != nil {
|
||||
s.logger.Errorf("could not fetch latest CCLF file for attribution status: %s", err)
|
||||
return time.Time{}, time.Time{}, err
|
||||
}
|
||||
|
||||
lastUpdated = latestFile.Timestamp
|
||||
switch fileType {
|
||||
case models.FileTypeDefault:
|
||||
expirationDate = latestFile.Timestamp.Add(s.stdCutoffDuration)
|
||||
case models.FileTypeRunout:
|
||||
expirationDate = latestFile.Timestamp.Add(s.rp.CutoffDuration)
|
||||
default:
|
||||
s.logger.Errorf("unsupported file type for fetching attribution expiration date")
|
||||
return lastUpdated, expirationDate, fmt.Errorf("unsupported file type for fetching attribution expiration date")
|
||||
}
|
||||
|
||||
if !timeConstraints.AttributionDate.IsZero() && timeConstraints.AttributionDate.Before(expirationDate) {
|
||||
return lastUpdated, timeConstraints.AttributionDate, nil
|
||||
}
|
||||
return lastUpdated, expirationDate, nil
|
||||
}
|
||||
|
||||
// FindOldCCLFFile finds an older CCLF file depending on passed in timestamps
|
||||
func (s *service) FindOldCCLFFile(ctx context.Context, cmsID string, since time.Time, cclfTimestamp time.Time) (cclfFileOldID uint, err error) {
|
||||
// Retrieve an older CCLF file for beneficiary comparison.
|
||||
// This should be older than cclfFileNew AND prior to the "since" parameter, if provided.
|
||||
//
|
||||
// e.g.
|
||||
// - If it’s October 2023
|
||||
// - and they request all beneficiary data “since January 1st, 2023"
|
||||
// - any beneficiaries added in 2023 are considered "new."
|
||||
//
|
||||
oldFileTime := since
|
||||
|
||||
// If the _since parameter is more recent than the latest CCLF file timestamp, e
|
||||
// set the old file's time to be older than the newest file's timestamp.
|
||||
if !since.IsZero() && cclfTimestamp.Sub(since) < 0 {
|
||||
oldFileTime = cclfTimestamp.Add(-1 * time.Second)
|
||||
}
|
||||
|
||||
cclfFileOld, err := s.GetLatestCCLFFile(
|
||||
ctx,
|
||||
cmsID,
|
||||
time.Time{},
|
||||
oldFileTime,
|
||||
models.FileTypeDefault,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return cclfFileOld.ID, nil
|
||||
}
|
||||
|
||||
func (s *service) GetQueJobs(ctx context.Context, args worker_types.PrepareJobArgs) (queJobs []*worker_types.JobEnqueueArgs, benesAttributed int, err error) {
|
||||
var (
|
||||
beneficiaries, newBeneficiaries []*models.CCLFBeneficiary
|
||||
jobs []*worker_types.JobEnqueueArgs
|
||||
)
|
||||
|
||||
// for default requests, runouts, or any requests where the Since parameter is
|
||||
// after a terminated ACO's attribution date, we should only retrieve exisiting benes
|
||||
switch args.ComplexDataRequestType {
|
||||
case constants.GetExistingBenes:
|
||||
beneficiaries, err = s.getBeneficiaries(ctx, args)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
case constants.GetNewAndExistingBenes:
|
||||
newBeneficiaries, beneficiaries, err = s.getNewAndExistingBeneficiaries(ctx, args)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// add new beneficiaries to the job queue; use a default time value to ensure
|
||||
// that we retrieve the full history for these beneficiaries
|
||||
jobs, err = s.createQueueJobs(ctx, args, time.Time{}, newBeneficiaries)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
queJobs = append(queJobs, jobs...)
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("unsupported RequestType %d", args.RequestType)
|
||||
}
|
||||
|
||||
// add existiing beneficiaries to the job queue
|
||||
jobs, err = s.createQueueJobs(ctx, args, args.Since, beneficiaries)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
benesAttributed = len(newBeneficiaries) + len(beneficiaries)
|
||||
args.Job.BenesAttributedToACO = benesAttributed
|
||||
err = s.repository.UpdateJob(ctx, args.Job)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to update job with benesAttributed: %w", err)
|
||||
}
|
||||
|
||||
queJobs = append(queJobs, jobs...)
|
||||
|
||||
return queJobs, benesAttributed, nil
|
||||
}
|
||||
|
||||
func (s *service) GetJobAndKeys(ctx context.Context, jobID uint) (*models.Job, []*models.JobKey, error) {
|
||||
j, err := s.repository.GetJobByID(ctx, jobID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keys, err := s.repository.GetJobKeys(ctx, jobID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return j, keys, nil
|
||||
}
|
||||
|
||||
func (s *service) GetJobKey(ctx context.Context, jobID uint, filename string) (*models.JobKey, error) {
|
||||
return s.repository.GetJobKey(ctx, jobID, filename)
|
||||
}
|
||||
|
||||
func (s *service) GetJobs(ctx context.Context, acoID uuid.UUID, statuses ...models.JobStatus) ([]*models.Job, error) {
|
||||
jobs, err := s.repository.GetJobs(ctx, acoID, statuses...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if jobs == nil {
|
||||
return nil, JobsNotFoundError{acoID, statuses}
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
type JobsNotFoundError struct {
|
||||
ACOID uuid.UUID
|
||||
StatusTypes []models.JobStatus
|
||||
}
|
||||
|
||||
func (e JobsNotFoundError) Error() string {
|
||||
return fmt.Sprintf("no Jobs found for acoID %s with job statuses %s",
|
||||
e.ACOID, e.StatusTypes)
|
||||
}
|
||||
|
||||
func (s *service) CancelJob(ctx context.Context, jobID uint) (uint, error) {
|
||||
// Assumes the job exists and retrieves the job by ID
|
||||
job, err := s.repository.GetJobByID(ctx, jobID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if job.Status == models.JobStatusPending || job.Status == models.JobStatusInProgress || job.Status == models.JobStatusCompleted {
|
||||
job.Status = models.JobStatusCancelled
|
||||
err = s.repository.UpdateJob(ctx, *job)
|
||||
if err != nil {
|
||||
return 0, ErrJobNotCancelled
|
||||
}
|
||||
return jobID, nil
|
||||
}
|
||||
|
||||
// Return 0, ErrJobNotCancellable when attempting to cancel a non-cancellable job.
|
||||
return 0, ErrJobNotCancellable
|
||||
}
|
||||
|
||||
// createQueueJobs expands a single "prepare job" request into concrete queue jobs by:
|
||||
// - choosing the effective resource + data types for the ACO/config (including BFD v3 rules)
|
||||
// - splitting beneficiaries into batches sized for the target resource type
|
||||
// - emitting one queue job per (resourceType × dataType × beneficiaryBatch)
|
||||
func (s *service) createQueueJobs(ctx context.Context, args worker_types.PrepareJobArgs, since time.Time, beneficiaries []*models.CCLFBeneficiary) (jobs []*worker_types.JobEnqueueArgs, err error) {
|
||||
sinceArg := formatSinceArg(since)
|
||||
resourceTypes, effectiveDataTypes, err := s.getEffectiveQueueJobConfig(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, resourceType := range resourceTypes {
|
||||
maxBeneficiaries, err := getMaxBeneCount(resourceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, beneficiaryIDs := range chunkBeneficiaryIDs(beneficiaries, maxBeneficiaries) {
|
||||
resourceJobs, err := s.createJobsForResourceChunk(ctx, args, sinceArg, resourceType, beneficiaryIDs, effectiveDataTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs = append(jobs, resourceJobs...)
|
||||
}
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
// formatSinceArg converts a time into the "_lastUpdated" query parameter format expected by Blue Button.
|
||||
// A zero time means "no since filter", which is represented as the empty string.
|
||||
func formatSinceArg(since time.Time) string {
|
||||
if since.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Persist in format ready for usage with _lastUpdated -- i.e., prepended with 'gt'.
|
||||
return "gt" + since.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// getEffectiveQueueJobConfig determines which resource types should be queued and which claim
|
||||
// data types should be requested, based on the matching ACO config and BFD API version.
|
||||
func (s *service) getEffectiveQueueJobConfig(args worker_types.PrepareJobArgs) ([]string, []string, error) {
|
||||
acoCfg, ok := s.GetACOConfigForID(args.CMSID)
|
||||
if !ok {
|
||||
return nil, nil, &bcdaerrors.InvalidACOConfigError{CMSID: args.CMSID}
|
||||
}
|
||||
|
||||
resourceTypes := append([]string(nil), args.ResourceTypes...)
|
||||
effectiveDataTypes := append([]string(nil), acoCfg.Data...)
|
||||
if args.BFDPath != constants.BFDV3Path {
|
||||
return resourceTypes, effectiveDataTypes, nil
|
||||
}
|
||||
|
||||
excluded := slices.Contains(s.v3NoPartialClaimsModels, acoCfg.Model)
|
||||
if !excluded && !slices.Contains(acoCfg.Data, constants.PartiallyAdjudicated) {
|
||||
effectiveDataTypes = append(effectiveDataTypes, constants.PartiallyAdjudicated)
|
||||
}
|
||||
|
||||
return resourceTypes, effectiveDataTypes, nil
|
||||
}
|
||||
|
||||
// chunkBeneficiaryIDs splits beneficiaries into fixed-size ID batches for queueing.
|
||||
func chunkBeneficiaryIDs(beneficiaries []*models.CCLFBeneficiary, maxBeneficiaries int) [][]string {
|
||||
if len(beneficiaries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
chunks := make([][]string, 0, (len(beneficiaries)+maxBeneficiaries-1)/maxBeneficiaries)
|
||||
currentChunk := make([]string, 0, maxBeneficiaries)
|
||||
|
||||
for _, beneficiary := range beneficiaries {
|
||||
currentChunk = append(currentChunk, fmt.Sprint(beneficiary.ID))
|
||||
if len(currentChunk) < maxBeneficiaries {
|
||||
continue
|
||||
}
|
||||
|
||||
chunks = append(chunks, currentChunk)
|
||||
currentChunk = make([]string, 0, maxBeneficiaries)
|
||||
}
|
||||
|
||||
if len(currentChunk) > 0 {
|
||||
chunks = append(chunks, currentChunk)
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
// createJobsForResourceChunk produces enqueue arguments for a single resource type and beneficiary batch,
|
||||
// creating a distinct job per compatible claim data type.
|
||||
func (s *service) createJobsForResourceChunk(ctx context.Context, args worker_types.PrepareJobArgs, sinceArg string, resourceType string, beneficiaryIDs []string, effectiveDataTypes []string) ([]*worker_types.JobEnqueueArgs, error) {
|
||||
resource, ok := GetClaimType(resourceType)
|
||||
if !ok {
|
||||
// This should never be possible, would have returned earlier.
|
||||
return nil, errors.New("Invalid resource type: " + resourceType)
|
||||
}
|
||||
|
||||
jobs := make([]*worker_types.JobEnqueueArgs, 0, len(effectiveDataTypes))
|
||||
for _, dataType := range effectiveDataTypes {
|
||||
if !resource.SupportsClaimType(dataType) {
|
||||
continue
|
||||
}
|
||||
|
||||
enqueueArgs, err := s.buildQueueJobArgs(ctx, args, sinceArg, beneficiaryIDs, resourceType, dataType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs = append(jobs, enqueueArgs)
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (s *service) buildQueueJobArgs(ctx context.Context, args worker_types.PrepareJobArgs, sinceArg string, beneficiaryIDs []string, resourceType string, dataType string) (*worker_types.JobEnqueueArgs, error) {
|
||||
jobID, err := safecast.ToInt(args.Job.ID)
|
||||
if err != nil {
|
||||
log.API.Errorln(err)
|
||||
}
|
||||
|
||||
enqueueArgs := worker_types.JobEnqueueArgs{
|
||||
ID: jobID,
|
||||
ACOID: args.ACOID.String(),
|
||||
CMSID: args.CMSID,
|
||||
BeneficiaryIDs: beneficiaryIDs,
|
||||
ResourceType: resourceType,
|
||||
Since: sinceArg,
|
||||
TypeFilter: args.TypeFilter,
|
||||
TransactionID: ctx.Value(middleware.CtxTransactionKey).(string),
|
||||
TransactionTime: getQueueJobTransactionTime(args, dataType),
|
||||
BBBasePath: args.BFDPath,
|
||||
DataType: dataType,
|
||||
}
|
||||
|
||||
if !s.setClaimsDate(&enqueueArgs, args) {
|
||||
return nil, &bcdaerrors.InvalidACOConfigError{CMSID: args.CMSID}
|
||||
}
|
||||
|
||||
return &enqueueArgs, nil
|
||||
}
|
||||
|
||||
func getQueueJobTransactionTime(args worker_types.PrepareJobArgs, dataType string) time.Time {
|
||||
// Partially-adjudicated data can lag adjudicated claims ingestion, so use job creation time.
|
||||
if dataType == constants.PartiallyAdjudicated {
|
||||
return args.CreationTime
|
||||
}
|
||||
|
||||
return args.Job.TransactionTime
|
||||
}
|
||||
|
||||
// Returns the beneficiaries associated with the latest CCLF file for the given request conditions,
|
||||
// split between existing beneficiaries and newly-attributed beneficiaries.
|
||||
func (s *service) getNewAndExistingBeneficiaries(ctx context.Context, args worker_types.PrepareJobArgs) (newBeneficiaries, beneficiaries []*models.CCLFBeneficiary, err error) {
|
||||
cclfFileNew, err := s.repository.GetCCLFFileByID(ctx, args.CCLFFileNewID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if cclfFileNew == nil {
|
||||
return nil, nil, fmt.Errorf("no CCLF8 file found for cmsID %s", args.CMSID)
|
||||
}
|
||||
|
||||
if !args.Since.IsZero() && cclfFileNew.CreatedAt.Sub(args.Since) < 0 {
|
||||
// Retrieve all of the benes associated with this CCLF file.
|
||||
benes, err := s.getBenesByFileID(ctx, cclfFileNew.ID, args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(benes) == 0 {
|
||||
return nil, nil, fmt.Errorf("found 0 new beneficiaries from CCLF8 file for cmsID %s cclfFiledID %d", args.CMSID, cclfFileNew.ID)
|
||||
}
|
||||
|
||||
return newBeneficiaries, benes, nil
|
||||
}
|
||||
|
||||
cclfFileOld, err := s.repository.GetCCLFFileByID(ctx, args.CCLFFileOldID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cclfFileOld == nil {
|
||||
s.logger.Infof("Unable to find CCLF8 File for cmsID %s prior to date: %s; all beneficiaries will be considered NEW", args.CMSID, args.Since)
|
||||
|
||||
newBeneficiaries, err = s.getBenesByFileID(ctx, cclfFileNew.ID, args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(newBeneficiaries) == 0 {
|
||||
return nil, nil, fmt.Errorf("found 0 new beneficiaries from CCLF8 file for cmsID %s cclfFiledID %d", args.CMSID, cclfFileNew.ID)
|
||||
}
|
||||
return newBeneficiaries, nil, nil
|
||||
}
|
||||
|
||||
oldMBIs, err := s.repository.GetCCLFBeneficiaryMBIs(ctx, cclfFileOld.ID)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to retrieve MBIs for cmsID %s cclfFileID %d %s", args.CMSID, cclfFileOld.ID, err.Error())
|
||||
}
|
||||
|
||||
// Retrieve all of the benes associated with this CCLF file.
|
||||
benes, err := s.getBenesByFileID(ctx, cclfFileNew.ID, args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(benes) == 0 {
|
||||
return nil, nil, fmt.Errorf("found 0 new or existing beneficiaries from CCLF8 file for cmsID %s cclfFiledID %d", args.CMSID, cclfFileNew.ID)
|
||||
}
|
||||
|
||||
// Split the results beteween new and old benes based on the existence of the bene in the old map
|
||||
oldMBIMap := make(map[string]struct{}, len(oldMBIs))
|
||||
for _, oldMBI := range oldMBIs {
|
||||
oldMBIMap[oldMBI] = struct{}{}
|
||||
}
|
||||
for _, bene := range benes {
|
||||
if _, ok := oldMBIMap[bene.MBI]; ok {
|
||||
beneficiaries = append(beneficiaries, bene)
|
||||
} else {
|
||||
newBeneficiaries = append(newBeneficiaries, bene)
|
||||
}
|
||||
}
|
||||
|
||||
return newBeneficiaries, beneficiaries, nil
|
||||
}
|
||||
|
||||
func (s *service) getBeneficiaries(ctx context.Context, args worker_types.PrepareJobArgs) ([]*models.CCLFBeneficiary, error) {
|
||||
cclfFile, err := s.repository.GetCCLFFileByID(ctx, args.CCLFFileNewID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cclfFile == nil {
|
||||
return nil, fmt.Errorf("no CCLF8 file found for cmsID %s", args.CMSID)
|
||||
}
|
||||
|
||||
benes, err := s.getBenesByFileID(ctx, cclfFile.ID, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(benes) == 0 {
|
||||
return nil, fmt.Errorf("found 0 beneficiaries from CCLF8 file for cmsID %s cclfFiledID %d", args.CMSID, cclfFile.ID)
|
||||
}
|
||||
|
||||
return benes, nil
|
||||
}
|
||||
|
||||
func (s *service) getBenesByFileID(ctx context.Context, cclfFileID uint, args worker_types.PrepareJobArgs) ([]*models.CCLFBeneficiary, error) {
|
||||
var (
|
||||
ignoredMBIs []string
|
||||
err error
|
||||
)
|
||||
if !s.sp.includeSuppressedBeneficiaries {
|
||||
upperBound := args.OptOutDate
|
||||
if args.OptOutDate.IsZero() {
|
||||
upperBound = time.Now()
|
||||
}
|
||||
|
||||
if cfg, ok := s.GetACOConfigForID(args.CMSID); ok {
|
||||
if !cfg.IgnoreSuppressions {
|
||||
ignoredMBIs, err = s.repository.GetSuppressedMBIs(ctx, s.sp.lookbackDays, upperBound)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retreive suppressedMBIs %s", err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, &bcdaerrors.InvalidACOConfigError{CMSID: args.CMSID}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
benes, err := s.repository.GetCCLFBeneficiaries(ctx, cclfFileID, ignoredMBIs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get beneficiaries %s", err.Error())
|
||||
}
|
||||
|
||||
return benes, nil
|
||||
}
|
||||
|
||||
// setClaimsDate computes the claims window to apply on the args
|
||||
func (s *service) setClaimsDate(args *worker_types.JobEnqueueArgs, prepareArgs worker_types.PrepareJobArgs) bool {
|
||||
// If the caller made a request for runout data
|
||||
// it takes precedence over any other claims date
|
||||
// that may be applied
|
||||
if prepareArgs.RequestType == constants.Runout {
|
||||
args.ClaimsWindow.Latest = s.rp.claimThruDate
|
||||
} else if !prepareArgs.ClaimsDate.IsZero() {
|
||||
args.ClaimsWindow.Latest = prepareArgs.ClaimsDate
|
||||
}
|
||||
|
||||
// Applies the earliest boundary (lower bound) from the first matching ACOConfig
|
||||
cfg, ok := s.GetACOConfigForID(prepareArgs.CMSID)
|
||||
if ok {
|
||||
args.ClaimsWindow.Earliest = cfg.LookbackTime(prepareArgs.CMSID)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Gets the priority for the job where the lower the number the higher the priority in the queue.
|
||||
// Priority is based on the request parameters that the job is executing on.
|
||||
// Note: River queue library requires a priority between 1 and 4 (inclusive)
|
||||
func (s *service) GetJobPriority(acoID string, resourceType string, sinceParam bool) int16 {
|
||||
if isPriorityACO(acoID) {
|
||||
return int16(1) // priority level for jobs for synthetic ACOs that are used for smoke testing
|
||||
} else {
|
||||
return int16(4) // default priority level for jobs
|
||||
}
|
||||
}
|
||||
|
||||
// GetACOConfigForID gets first matching currently loaded ACOConfig for the specified cmsID
|
||||
func (s *service) GetACOConfigForID(cmsID string) (*ACOConfig, bool) {
|
||||
for _, cfg := range s.acoConfigs {
|
||||
if cfg.patternExp.MatchString(cmsID) {
|
||||
return &cfg, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// IsV3NoPartialClaimsModel checks if the provided model is listed in V3NoPartialClaimsModels
|
||||
func (s *service) IsV3NoPartialClaimsModel(model string) bool {
|
||||
return slices.Contains(s.v3NoPartialClaimsModels, model)
|
||||
}
|
||||
|
||||
// Checks to see if an ACO is priority ACO based on a regex pattern provided by an
|
||||
// environment variable.
|
||||
func isPriorityACO(acoID string) bool {
|
||||
if priorityACOPattern := conf.GetEnv("PRIORITY_ACO_REG_EX"); priorityACOPattern != "" {
|
||||
priorityACORegex := regexp.MustCompile(priorityACOPattern)
|
||||
if priorityACORegex.MatchString(acoID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getMaxBeneCount(requestType string) (int, error) {
|
||||
const (
|
||||
BCDA_FHIR_MAX_RECORDS_EOB_DEFAULT = 50
|
||||
BCDA_FHIR_MAX_RECORDS_PATIENT_DEFAULT = 5000
|
||||
BCDA_FHIR_MAX_RECORDS_COVERAGE_DEFAULT = 4000
|
||||
BCDA_FHIR_MAX_RECORDS_CLAIM_DEFAULT = 4000
|
||||
BCDA_FHIR_MAX_RECORDS_CLAIMRESPONSE_DEFAULT = 4000
|
||||
)
|
||||
var envVar string
|
||||
var defaultVal int
|
||||
|
||||
switch requestType {
|
||||
case "ExplanationOfBenefit":
|
||||
envVar = "BCDA_FHIR_MAX_RECORDS_EOB"
|
||||
defaultVal = BCDA_FHIR_MAX_RECORDS_EOB_DEFAULT
|
||||
case "Patient":
|
||||
envVar = "BCDA_FHIR_MAX_RECORDS_PATIENT"
|
||||
defaultVal = BCDA_FHIR_MAX_RECORDS_PATIENT_DEFAULT
|
||||
case "Coverage":
|
||||
envVar = "BCDA_FHIR_MAX_RECORDS_COVERAGE"
|
||||
defaultVal = BCDA_FHIR_MAX_RECORDS_COVERAGE_DEFAULT
|
||||
case "Claim":
|
||||
envVar = "BCDA_FHIR_MAX_RECORDS_CLAIM"
|
||||
defaultVal = BCDA_FHIR_MAX_RECORDS_CLAIM_DEFAULT
|
||||
case "ClaimResponse":
|
||||
envVar = "BCDA_FHIR_MAX_RECORDS_CLAIM_RESPONSE"
|
||||
defaultVal = BCDA_FHIR_MAX_RECORDS_CLAIMRESPONSE_DEFAULT
|
||||
default:
|
||||
err := errors.New("invalid request type")
|
||||
return -1, err
|
||||
}
|
||||
maxBeneficiaries := utils.GetEnvInt(envVar, defaultVal)
|
||||
|
||||
return maxBeneficiaries, nil
|
||||
}
|
||||
|
||||
func (s *service) GetLatestCCLFFile(ctx context.Context, cmsID string, earlierTime time.Time, laterTime time.Time, fileType models.CCLFFileType) (*models.CCLFFile, error) {
|
||||
cclfFile, err := s.repository.GetLatestCCLFFile(ctx, cmsID, constants.CCLF8FileNum, constants.ImportComplete, earlierTime, laterTime, fileType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cclfFile == nil {
|
||||
return nil, CCLFNotFoundError{8, cmsID, fileType, earlierTime}
|
||||
}
|
||||
|
||||
return cclfFile, nil
|
||||
}
|
||||
|
||||
// GetTimeConstraints searches for any time bounds that we should apply on the associated ACO
|
||||
func (s *service) GetTimeConstraints(ctx context.Context, cmsID string) (TimeConstraints, error) {
|
||||
var constraint TimeConstraints
|
||||
aco, err := s.repository.GetACOByCMSID(ctx, cmsID)
|
||||
if err != nil {
|
||||
return constraint, fmt.Errorf("failed to retrieve aco: %w", err)
|
||||
}
|
||||
|
||||
// If aco is not terminated, then we should not apply any time constraints
|
||||
if aco.TerminationDetails == nil {
|
||||
return constraint, nil
|
||||
}
|
||||
|
||||
constraint.AttributionDate = aco.TerminationDetails.AttributionDate()
|
||||
constraint.ClaimsDate = aco.TerminationDetails.ClaimsDate()
|
||||
constraint.OptOutDate = aco.TerminationDetails.OptOutDate()
|
||||
return constraint, nil
|
||||
}
|
||||
|
||||
type CCLFNotFoundError struct {
|
||||
FileNumber int
|
||||
CMSID string
|
||||
FileType models.CCLFFileType
|
||||
CutoffTime time.Time
|
||||
}
|
||||
|
||||
func (e CCLFNotFoundError) Error() string {
|
||||
return fmt.Sprintf("no CCLF%d file found for cmsID %s fileType %d cutoffTime %s",
|
||||
e.FileNumber, e.CMSID, e.FileType, e.CutoffTime.String())
|
||||
}
|
||||
|
||||
var (
|
||||
ErrJobNotCancelled = goerrors.New("job was not cancelled due to internal server error")
|
||||
ErrJobNotCancellable = goerrors.New("job was not cancelled because it is not Pending or In Progress")
|
||||
)
|
||||
111
dev/vendor/bcda-app/conf/configs/prod.yml
vendored
Normal file
111
dev/vendor/bcda-app/conf/configs/prod.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
aco_config:
|
||||
# Kidney Care Choices (KCC) Model - Comprehensive Kidney Care Contracting
|
||||
- model: 'CKCC' # Active, managed by 4i
|
||||
name_pattern: '^C\d{4}$'
|
||||
performance_year_transition: '01/01'
|
||||
lookback_period: 2
|
||||
data:
|
||||
- 'adjudicated'
|
||||
# Kidney Care Choices (KCC) Model - Kidney Care First
|
||||
- model: 'KCF' # Ended, managed by 4i
|
||||
name_pattern: '^K\d{4}$'
|
||||
performance_year_transition: '01/01'
|
||||
lookback_period: 2
|
||||
data:
|
||||
- 'adjudicated'
|
||||
# ACO REACH Model, formerly Global and Professional Direct Contracting Model (GPDC) Model
|
||||
- model: 'DC' # Active, managed by 4i
|
||||
name_pattern: '^D\d{4}$'
|
||||
performance_year_transition: '04/01'
|
||||
lookback_period: 3
|
||||
data:
|
||||
- 'partially-adjudicated'
|
||||
- 'adjudicated'
|
||||
# Medicare Shared Savings Program
|
||||
- model: 'SSP' # Active, managed by ACO-MS
|
||||
name_pattern: '^A\d{4}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
# Comprehensive ESRD Care (CEC) Model
|
||||
- model: 'CEC' # Ended
|
||||
name_pattern: '^E\d{4}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
# Next Generation ACO Model
|
||||
- model: 'NGACO' # Ended
|
||||
name_pattern: '^V\d{3}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
# A config for BCDA's test entities
|
||||
- model: 'TEST' # Active, managed by BCDA Admin
|
||||
name_pattern: '^TEST\d{3}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
- 'partially-adjudicated'
|
||||
# Maryland Total Cost of Care (MD TCoC) Model
|
||||
- model: 'TCOCMD' # Ended, managed by BCDA Admin
|
||||
name_pattern: '^CT\d{4,6}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
- 'partially-adjudicated'
|
||||
attribution_file:
|
||||
file_type: 'csv'
|
||||
name_pattern: '(P|T)\.(PCPB)\.(M)([0-9][0-9])(\d{2})\.(D\d{6}\.T\d{6})\d'
|
||||
model_identifier: 'PCPB'
|
||||
file_performance_year: 4
|
||||
file_date: 6
|
||||
metadata_matches: 7
|
||||
ignore_suppressions: true
|
||||
# CMS Data Aggregation Contract
|
||||
- model: 'CDAC' # Ended, managed by IHP
|
||||
name_pattern: '^DA\d{4}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
- 'partially-adjudicated'
|
||||
attribution_file:
|
||||
file_type: 'csv'
|
||||
name_pattern: '(P|T)\.(BCD)\.(DA\d{4}).(MBIY)(\d{2})\.(D\d{6}\.T\d{6})\d'
|
||||
model_identifier: 'BCD'
|
||||
file_performance_year: 5
|
||||
file_date: 6
|
||||
metadata_matches: 7
|
||||
# GUIDE (Guiding an Improved Dementia Experience) Model
|
||||
- model: 'GUIDE' # Active, managed by IHP
|
||||
name_pattern: '^GUIDE-\d{4}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
attribution_file:
|
||||
file_type: 'csv'
|
||||
name_pattern: '(P|T)\.(GUIDE)\.(GUIDE-)(\d{4})\.(Y)(\d{2})\.(D\d{6}\.T\d{6})\d'
|
||||
model_identifier: 'GUIDE'
|
||||
file_performance_year: 6
|
||||
file_date: 7
|
||||
metadata_matches: 8
|
||||
# ACCESS (Advancing Chronic Care with Effective, Scalable Solutions) Model
|
||||
- model: 'ACCESS' # Active, managed by IHP
|
||||
name_pattern: '^ACCES\d{5}$' # only one 's' due to character limits
|
||||
lookback_period: 3
|
||||
data:
|
||||
- 'adjudicated'
|
||||
attribution_file:
|
||||
file_type: 'csv'
|
||||
name_pattern: '(P|T)\.(ACCESS)\.(ACCES\d{5})\.(Y)(\d{2})\.(D\d{6}\.T\d{6})\d'
|
||||
model_identifier: 'ACCESS'
|
||||
file_performance_year: 5
|
||||
file_date: 6
|
||||
metadata_matches: 7
|
||||
# Increasing Organ Transplant Access (IOTA) Model
|
||||
- model: 'IOTA' # Active, managed via 4i
|
||||
name_pattern: '^IOTA\d{3}$'
|
||||
data:
|
||||
- 'adjudicated'
|
||||
- 'partially-adjudicated'
|
||||
rate_limit_config:
|
||||
all: true
|
||||
acos: []
|
||||
v1_v2_deny_regexes:
|
||||
- '^IOTA\d{3}'
|
||||
- '^ACCES\d{5}$'
|
||||
- '^GUIDE-\d{4}$'
|
||||
v3_no_partial_claims_models:
|
||||
- 'GUIDE'
|
||||
BIN
dev/vendor/bcda-site/BCDA_Partially_Adjudicated_Data_Dictionary.xlsx
vendored
Normal file
BIN
dev/vendor/bcda-site/BCDA_Partially_Adjudicated_Data_Dictionary.xlsx
vendored
Normal file
Binary file not shown.
BIN
dev/vendor/bcda-site/unmapped-fields-between-cclf-and-bcda.xlsx
vendored
Normal file
BIN
dev/vendor/bcda-site/unmapped-fields-between-cclf-and-bcda.xlsx
vendored
Normal file
Binary file not shown.
121
dev/vendor/bcda-static-site/LICENSE
vendored
Normal file
121
dev/vendor/bcda-static-site/LICENSE
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
22
dev/vendor/bcda-static-site/src/_announcements/2025-py-data-limit-ended.md
vendored
Normal file
22
dev/vendor/bcda-static-site/src/_announcements/2025-py-data-limit-ended.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
layout: announcement
|
||||
page_title: "End of BCDA data limitations"
|
||||
description: "Performance year (PY) 2026 data is now accessible."
|
||||
show-side-nav: false
|
||||
in-page-nav: true
|
||||
lead_paragraph: "Performance year (PY) 2026 data now available."
|
||||
published_date: 2026-02-12
|
||||
---
|
||||
|
||||
All model entities can now access PY 2026 data using the `/Group/all` and `/Patient` endpoints.
|
||||
|
||||
- Use the `all` identifier for `/Group` and `/Patient` endpoint requests.
|
||||
- Use the `all` identifier **for your first PY 2026 data request.** There will be no service date restrictions for this data.
|
||||
|
||||
## Requesting PY 2025 runout data
|
||||
|
||||
Use the `runout` identifier separately with the `/Group` endpoint. This will return data for enrollees who were attributed to your organization in PY 2025, but not PY 2026\.
|
||||
|
||||
- Use the `_since` parameter in conjunction with the `runout` identifier to limit data to updates occurring since your last runout request.
|
||||
- The `runout` identifier will be updated each month up to and including July 2026.
|
||||
- After July 2026, new data for enrollees who were attributed to your organization in PY 2025, but not PY 2026, will not be included.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
layout: announcement
|
||||
page_title: "BCDA data limited in Jan 2026 for yearly updates"
|
||||
description: "Claims with 2026 service dates will be unavailable throughout January 2026."
|
||||
show-side-nav: false
|
||||
in-page-nav: true
|
||||
lead_paragraph: "Claims with 2026 service dates will be unavailable throughout January 2026."
|
||||
published_date: 2025-12-03
|
||||
---
|
||||
|
||||
Certain BCDA data is unavailable every January while CMS updates attributed beneficiaries for the new performance year (PY). Normal functionality will be restored to individual entities as soon as their data is ready. This typically takes 1 month.
|
||||
|
||||
PY 2026 starts Jan 1, 2026 and ends Dec 31, 2026.
|
||||
|
||||
## What data will be unavailable in Jan 2026?
|
||||
|
||||
Requests to `/Group/all` and `/Patient` endpoints will be disabled while we wait for updated attribution info for your model entity.
|
||||
|
||||
## How should I request PY 2025 runout data?
|
||||
|
||||
To request runout data for enrollees attributed to your organization in PY 2025:
|
||||
|
||||
* Use the `runout` identifier with the `/Group` endpoint.
|
||||
* Claims with a service date in 2026 will be excluded. Data returned will have a service date no later than December 31, 2025.
|
||||
* Use the [\_since parameter]({{ '/api-documentation/filter-claims-data.html#the-_since-parameter' | relative_url }}) with the `runout` identifier to return data updated since your last runout request. [Review our documentation on the runout identifier]({{ '/api-documentation/filter-claims-data.html#the-runout-identifier' | relative_url }}).
|
||||
|
||||
## How will I know when data is available again?
|
||||
|
||||
We’ll notify you in the [BCDA Google Group](https://groups.google.com/g/bc-api) when the limitation ends for each supported model. At that point, you can use the `all` identifier to request data, including 2026 claims, with the `/Group` and `/Patient` endpoints as usual.
|
||||
|
||||
## Examples of requests/outcomes to expect in Jan 2026
|
||||
|
||||
- A caller makes a request to the `/Patient` or `/Group` endpoint using the `all` identifier.
|
||||
- GET `/api/v2/Patient/$export`
|
||||
- GET `/api/v2/Group/all/$export`
|
||||
|
||||
- The caller receives a `404 Not Found` response and an `OperationOutcome` error.
|
||||
- An example `OperationOutcome.fhir.json` is attached.
|
||||
- Callers should use the `/api/v2/Group/runout/$export` endpoint.
|
||||
|
||||
- A caller makes a request to the `/Group` endpoint using the `runout` identifier.
|
||||
- GET `/api/v2/Group/runout/$export`
|
||||
|
||||
- The caller receives a `202 Accepted` response.
|
||||
- Content-Location header sent with the job location.
|
||||
- `Content-Location: /api/v2/jobs/{job_id}`
|
||||
|
||||
- A caller polls the `/Jobs` endpoint until the job completes.
|
||||
- GET `/api/v2/jobs/{job_id}`
|
||||
|
||||
- The caller can now download the job data.
|
||||
- GET `/data/2/output.json`
|
||||
- All claims data will be capped at December 31, 2025.
|
||||
|
||||
## Need help?
|
||||
|
||||
Keep an eye on the [Google Group conversation](https://groups.google.com/g/bc-api/c/0v78i8P1sKo) to ask questions and read updates about BCDA data availability. If you can’t find what you need, send an email to [bcapi@cms.hhs.gov](mailto:bcapi@cms.hhs.gov).
|
||||
61
dev/vendor/bcda-static-site/src/_announcements/bcda-launches-v3.md
vendored
Normal file
61
dev/vendor/bcda-static-site/src/_announcements/bcda-launches-v3.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
layout: announcement
|
||||
page_title: "BCDA v3: Enhanced timeliness and performance for value-based care"
|
||||
description: "Core improvements to data sources and alignment mean timelier, more person-centered care. "
|
||||
show-side-nav: false
|
||||
in-page-nav: true
|
||||
lead_paragraph: "Core improvements to data sources and alignment mean timelier, more person-centered care."
|
||||
published_date: 2026-07-01
|
||||
---
|
||||
|
||||
Centers for Medicare & Medicaid Services (CMS) is excited to announce version 3 (v3) of its [FHIR](http://hl7.org/fhir/) Medicare claims data sharing platform. v3 drives improvements to CMS Beneficiary Claims Data API (BCDA), [Blue Button API](https://bluebutton.cms.gov/), and [Claims Data to Part D Sponsors API (AB2D)](https://ab2d.cms.gov/). [BCDA v3]({{ '/about/introducing-v3.html' | relative_url }}) helps model entities coordinate care more effectively, track performance on CMS quality measures, and improve patient outcomes.
|
||||
|
||||
## BCDA v3 improvements
|
||||
|
||||
With BCDA v3, model entities get patient claims data earlier, allowing them to identify issues and intervene sooner. v3 strengthens alignment with Fast Healthcare Interoperability Resources (FHIR) standards and other CMS data sources, reducing time and money spent on integrations and reconciliation. Driving these improvements are:
|
||||
|
||||
- A new, single source of Medicare claims data
|
||||
- Better alignment with sources such as Claim and Claim Line Feed (CCLF) files
|
||||
|
||||
These fundamental changes improve data frequency, tracking, filtering, mapping, and FHIR conformance.
|
||||
|
||||
### Timelier, more frequent updates
|
||||
|
||||
v3 offers timelier updates to patient and coverage data, meaning:
|
||||
|
||||
- Shorter time between full processing (adjudication) and data availability
|
||||
- Increased frequency of updates to patient and coverage information
|
||||
|
||||
### Easier claims tracking
|
||||
|
||||
BCDA v3 makes it easier to track claims through the adjudication cycle. It uses one FHIR resource instead of 3 to represent fully and partially processed versions of a claim. v3 also simplifies the way BCDA denotes claims status.
|
||||
|
||||
### Enhanced filtering capabilities
|
||||
|
||||
The new [_typeFilter query parameter]({{ '/api-documentation/filter-claims-data.html#the-_typefilter-parameter' | relative_url }}) provides additional options for complex data workflows.
|
||||
|
||||
### Improved conformance with select FHIR Implementation Guides
|
||||
|
||||
v3 standardizes extensions while providing better FHIR conformance. CodeSystems are represented using `StructureDefinition` and `CodeSystem` resources.
|
||||
|
||||
### Simplified, reliable data mapping
|
||||
|
||||
BCDA v3 data aligns more closely with CCLF files, and generates its Data Dictionary automatically.
|
||||
|
||||
## The IDR: a single source of claims data
|
||||
|
||||
v3 consolidates upstream data to a single data source, the Integrated Data Repository (IDR). This change:
|
||||
|
||||
- Reduces mismatches between BCDA resources and CCLF files
|
||||
- Resolves missing data for newly attributed enrollees
|
||||
- Fixes issues for enrollees assigned more than one `BENE_ID`
|
||||
- Simplifies linking between partially and fully adjudicated claims
|
||||
- Uses consistent claim identifiers across all adjudication phases
|
||||
|
||||
## What this means for healthcare organizations
|
||||
|
||||
Through a unified data structure and consolidated data sources, BCDA v3 can reduce development time, improve data quality, and ensure providers have up-to-date information about every Medicare patient.
|
||||
|
||||
## What this means for current users
|
||||
|
||||
The v1 and v2 endpoints will be disabled on July 30, 2027. Refer to the [v3 Migration Guide]({{ '/api-documentation/how-to-migrate-v3.html' | relative_url }}) for steps and support on upgrading to v3.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
layout: announcement
|
||||
page_title: "BCDA performance improvement to speed up job requests"
|
||||
show-side-nav: false
|
||||
in-page-nav: true
|
||||
published_date: 2024-12-01
|
||||
description: "Reducing resources file and increasing rate limits speeds up the time to complete long-running jobs with BCDA."
|
||||
lead_paragraph: "New changes decrease the time it takes to complete long-running jobs. "
|
||||
custom_excerpt: "New changes decrease the time it takes to complete long-running jobs. "
|
||||
---
|
||||
|
||||
We've decreased the time it takes to complete long-running jobs by reducing the number of EOB resources per ndjson file. We've also increased the rate limit on API requests by 10X to accommodate the additional files.
|
||||
|
||||
What's changing?
|
||||
- EOB resources per ndjson file are reduced from 200 to 50.
|
||||
- We've increased the rate limit from 300 to 3,000 per 5 minutes from a single IP address.
|
||||
|
||||
Why is it changing?
|
||||
- The change in EOB file volume decreases the time it takes to complete long-running jobs like requests for historical data.
|
||||
- The API rate limit increase lowers likelihood of having your requests throttled.
|
||||
|
||||
What can you do to prepare?
|
||||
- There's no action required. It can be helpful to revisit our guidance on [handling 429 errors]({{ "/api-documentation/access-claims-data.html#response-example-too-many-requests" | relative_url }}) caused by exceeding the API rate limit.
|
||||
25
dev/vendor/bcda-static-site/src/_announcements/bcda-v3-expanded-access-general-availability.md
vendored
Normal file
25
dev/vendor/bcda-static-site/src/_announcements/bcda-v3-expanded-access-general-availability.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
layout: announcement
|
||||
page_title: "BCDA v3: Expanded access, general availability"
|
||||
description: "Over 600 new organizations eligible for BCDA in time for v3 general availability"
|
||||
show-side-nav: false
|
||||
in-page-nav: true
|
||||
lead_paragraph: "Over 600 new organizations eligible for BCDA in time for v3 general availability"
|
||||
published_date: 2026-10-07
|
||||
---
|
||||
|
||||
Summer 2026 increased the reach of FHIR-enabled Medicare claims data thanks to new [Innovation Models](https://www.cms.gov/priorities/innovation/models) and the general availability release of BCDA v3.
|
||||
|
||||
## 3 new payment models can access BCDA
|
||||
|
||||
As of July 2026, participants in the [ACCESS](https://www.cms.gov/priorities/innovation/innovation-models/access), [GUIDE](https://www.cms.gov/priorities/innovation/innovation-models/guide), and [IOTA](https://www.cms.gov/priorities/innovation/innovation-models/iota) models have access to Medicare claims data. Timely claims data will help model participants treat and manage complex conditions. These include chronic conditions, dementia, and needs arising around kidney transplants.
|
||||
|
||||
## General availability means faster access to v3
|
||||
|
||||
With the phased rollout complete (from early adopter program, to limited release, to general availability), eligible organizations can now initiate their own access to BCDA v3. As announced in the [BCDA Google Group](https://groups.google.com/g/bc-api), you no longer need to submit a request to [onboard]({{ '/production-access.html' | relative_url }}) or [migrate]({{ '/api-documentation/how-to-migrate-v3.html' | relative_url }}) in order to get started.
|
||||
|
||||
## Why move to v3?
|
||||
|
||||
With v3, platform-level improvements lead to earlier access to claims data. Earlier access supports timely follow-ups and interventions. A closer alignment between v3 and other CMS data sources supports a more detailed, longitudinal picture of health for people with Medicare.
|
||||
|
||||
Join the [BCDA Google Group](https://groups.google.com/g/bc-api) for additional news and support.
|
||||
612
dev/vendor/bcda-static-site/src/_pages/api-documentation/access-claims-data.md
vendored
Normal file
612
dev/vendor/bcda-static-site/src/_pages/api-documentation/access-claims-data.md
vendored
Normal file
@@ -0,0 +1,612 @@
|
||||
---
|
||||
layout: api-docs
|
||||
page_title: "How to Access Claims Data"
|
||||
seo_title: ""
|
||||
description: "Learn how to access Medicare enrollees' Parts A, B, and D claims data in the BCDA production environment for performance tracking and risk analysis."
|
||||
in-page-nav: true
|
||||
feedback_id: "f179685c"
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
Learn how to access claims data for both the sandbox and production environments and use Beneficiary Claims Data API (BCDA) endpoints.
|
||||
|
||||
The sandbox and production environments follow similar instructions. They support the same workflow, endpoints, parameters, and resource types.
|
||||
|
||||
<table class="usa-table usa-table--borderless margin-bottom-6">
|
||||
<caption class="usa-sr-only">Sandbox and Production environments comparison</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Sandbox</th>
|
||||
<th scope="col">Production</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Available to everyone via test credentials</td>
|
||||
<td>Must <a href="{{ '/production-access.html' | relative_url }}">complete the steps</a> for production credentials</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contains test claims data</td>
|
||||
<td>Contains real Medicare enrollee data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sandbox.bcda.cms.gov</code></td>
|
||||
<td><code>api.bcda.cms.gov</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="usa-alert usa-alert--info">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__heading text-bold">BCDA recommends using v3 of the API</p>
|
||||
<p class="usa-alert__text">This is the latest version which follows the <a href="https://hl7.org/fhir/R4/" target="_blank" rel="noopener noreferrer">FHIR R4 specification</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Instructions
|
||||
|
||||
### 1. Get a bearer token
|
||||
|
||||
You will need a [bearer token]({{ '/api-documentation/get-a-bearer-token.html' | relative_url }}) to call the API. The process requires credentials, which are formatted as a client ID and client secret.
|
||||
|
||||
<div class="usa-alert usa-alert--warning usa-alert--slim">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text">Jobs will not be interrupted when the bearer token <a href="{{ '/api-documentation/get-a-bearer-token.html#troubleshooting' | relative_url }}">expires</a>. In progress and queued jobs will continue to run.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### 2. Start a job
|
||||
|
||||
Make a `GET` request to the /Group or /Patient endpoint to start a data export job. The examples below are sandbox curl requests to /Group. Follow along in your terminal or using a tool like Postman.
|
||||
|
||||
#### Request all resource types
|
||||
|
||||
By default, the `GET` request returns all available [resource types]({{ '/bcda-data.html#resource-types' | relative_url }}).
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/all/$export
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
Use the [_type parameter]({{ '/api-documentation/filter-claims-data.html' | relative_url }}) to specify which resource types you'd like returned.
|
||||
|
||||
#### Request header
|
||||
|
||||
The header must contain your bearer token. You may receive a `401` response if your credentials are invalid or expired. `Bearer ` must be included in the header with a capital B and followed by a space.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
Accept: application/fhir+json
|
||||
Prefer: respond-async
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### Example curl commands to start a job
|
||||
|
||||
- Combine your `GET` request for resources with the request header.
|
||||
- The dollar sign `$` before `export` in the URL indicates the endpoint is an <a href="https://hl7.org/fhir/R4/operations.html" target="_blank" rel="noopener noreferrer">operation</a>, rather than a CRUD interaction.
|
||||
- PowerShell users will need to replace backslash characters `\` with backticks ( \` ) to properly escape the `$export` operation.
|
||||
|
||||
<div class="usa-alert usa-alert--info usa-alert--no-icon">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text text-bold">Remember to use the correct URL for your environment</p>
|
||||
<p class="usa-alert__text">Use <code>sandbox.bcda.cms.gov</code> to access the sandbox or <code>api.bcda.cms.gov</code> to access the production environment.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- snippet x3 -->
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export?_type=ExplanationOfBenefit,Patient" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export?_type=Patient" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Response example: successful request
|
||||
|
||||
A `202 Accepted` response with a Content-Location header indicates a successful request.
|
||||
|
||||
#### Response header example
|
||||
|
||||
You'll need the job ID in the Content-Location header to check your job status.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Content-Location: https://sandbox.bcda.cms.gov/api/v3/jobs/{job_id}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Response example: too many requests
|
||||
|
||||
A `429 Too Many Requests` response can occur due to 2 reasons:
|
||||
|
||||
1. Making too many HTTP requests within a period of time
|
||||
2. Trying to recreate jobs already marked as "In-Progress.” For reference, you can view both existing and past jobs using the [/jobs endpoint]({{ '/api-documentation/access-claims-data.html' | relative_url }}#request-job-history).
|
||||
|
||||
Wait until the period of time specified in the `Retry-After: <delay-seconds>` header passes before making any more requests. This makes sure your client can adapt without manual intervention, even if the rate-limiting parameters change.
|
||||
|
||||
### 3. Check job status
|
||||
|
||||
Make a `GET` request to check the status using the job ID from step 2. You may need another bearer token if it's been over 20 minutes since it was generated.
|
||||
|
||||
#### Request to check the job status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET https://sandbox.bcda.cms.gov/api/v3/jobs/{job_id}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request header
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
Accept: application/fhir+json
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### curl command to check the job status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/jobs/{job_id}" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Response example: incomplete job
|
||||
|
||||
A `202` response indicates your job is still processing. The status will change to `200 OK` when the export is complete and the data is ready for download.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
202 Accepted
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Response header example: incomplete job
|
||||
|
||||
The X-Progress will have a percentage indicating your estimated progress.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
X-Progress: In Progress, 80%
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Response example: completed job
|
||||
|
||||
You'll receive a `200 OK` response with the output URL(s) needed to download the data. In the example URLs below, `42` indicates the job ID.
|
||||
|
||||
There is a separate URL for each resource type requested. The following example shows a request for all resource types.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"transactionTime": "2026-12-09T20:44:01.705398Z",
|
||||
"request": "https://sandbox.bcda.cms.gov/api/v3/Group/all/$export",
|
||||
"requiresAccessToken": true,
|
||||
"output": [
|
||||
{
|
||||
"type": "ExplanationOfBenefit",
|
||||
"url": "https://sandbox.bcda.cms.gov/data/42/afd22dfa-c239-4063-8882-eb2712f9f638.ndjson"
|
||||
},
|
||||
{
|
||||
"type": "Coverage",
|
||||
"url": "https://sandbox.bcda.cms.gov/data/42/f76a0b76-48ed-4033-aad9-d3eec37e7e83.ndjson"
|
||||
},
|
||||
{
|
||||
"type": "Patient",
|
||||
"url": "https://sandbox.bcda.cms.gov/data/42/f92dcf16-63a2-448e-a12a-3bf677f966ed.ndjson"
|
||||
}
|
||||
],
|
||||
"error": [],
|
||||
"JobID": 42
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" %}
|
||||
|
||||
### 4. Download the data
|
||||
|
||||
Make a `GET` request to download your data using the URL(s) from step 3.
|
||||
|
||||
If you're downloading from more than 1 URL, make multiple download requests concurrently to save time. Large files may take significantly longer to download.
|
||||
|
||||
<div class="usa-alert usa-alert--info usa-alert--no-icon">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text text-bold">Files expire after 24 hours</p>
|
||||
<p class="usa-alert__text">You can download these files using a new bearer token. After 24 hours, the files will expire and you will need to restart the job.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
#### Request to download the data
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET https://sandbox.bcda.cms.gov/data/{job_id}/{file_name}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request header: compressed data files
|
||||
|
||||
Request compressed data files with the optional `Accept-Encoding: gzip` header in your requests for faster download times. Afterward, decompress (unzip) the files into NDJSON format.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
Accept-Encoding: gzip
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### curl command to download the data
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/data/{job_id}/{file_name}" \
|
||||
-H "Accept-Encoding: gzip" \
|
||||
-H "Authorization: Bearer {bearer_token}"
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
If some of the data can't be exported due to errors, details can be found at the URLs in the error field. The errors are provided in an NDJSON file as FHIR OperationOutcome resources.
|
||||
|
||||
#### Response example
|
||||
|
||||
By default, you'll receive the requested data as FHIR resources in NDJSON format. Each resource type will appear as a separate, labeled file.
|
||||
|
||||
<div class="usa-alert usa-alert--info usa-alert--no-icon usa-alert--slim">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text">Test data from the sandbox contains only negative Patient IDs.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol>
|
||||
<li><a href="{{ '/assets/downloads/v3_ExplanationOfBenefit.ndjson' | relative_url }}" data-tealium="download">ExplanationOfBenefit.ndjson {% include sprite.html icon="file_download" class="text-middle" size="2" %}</a></li>
|
||||
<li><a href="{{ '/assets/downloads/v3_Patient.ndjson' | relative_url }}" data-tealium="download">Patient.ndjson {% include sprite.html icon="file_download" class="text-middle" size="2" %}</a></li>
|
||||
<li><a href="{{ '/assets/downloads/v3_Coverage.ndjson' | relative_url }}" data-tealium="download">Coverage.ndjson {% include sprite.html icon="file_download" class="text-middle" size="2" %}</a></li>
|
||||
</ol>
|
||||
|
||||
## Other BCDA endpoints
|
||||
|
||||
### Cancel a job
|
||||
|
||||
Cancel any active job. If the request is successful, you'll receive a `202` response.
|
||||
|
||||
#### Request to cancel a job
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
DELETE /api/v3/jobs/{job_id}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request header
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### curl command to cancel a job
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X DELETE "https://sandbox.bcda.cms.gov/api/v3/jobs/{job_id}" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Authorization: Bearer {bearer_token}"
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
### Request job history
|
||||
|
||||
Retrieve details on your organization's historical requests, including the start and end datetime, unique ID, original valueString request, and status.
|
||||
|
||||
#### Request to retrieve all past jobs
|
||||
|
||||
If your organization has no jobs to return, you'll receive a `404 ERROR` response.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/jobs
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request that filters jobs by end state
|
||||
|
||||
Supply the _status parameter to filter for jobs with a specific end state. BCDA jobs have 9 possible end states: Completed, Archived, Expired, Failed, FailedExpired, In Progress, Pending, Cancelled, and CancelledExpired.
|
||||
|
||||
These are how the job end states map to the 4 supported values you can receive in the response body:
|
||||
|
||||
- `Archived`, `Expired`, `Completed` → `completed`
|
||||
- `FailedExpired`, `Failed` → `failed`
|
||||
- `Pending`, `In Progress` → `in-progress`
|
||||
- `CancelledExpired`, `Cancelled` → `cancelled`
|
||||
|
||||
The example below is a filtered request for all past archived jobs. If any are found, the response will list the status as `completed`. Even so, the filter will only return archived jobs; it will exclude expired and completed jobs.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/jobs?_status=Archived
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request header
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
Accept: application/fhir+json
|
||||
Prefer: respond-async
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### curl command to check the job status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/jobs" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Response example: completed job
|
||||
|
||||
The response will contain a bundle of resources for each historical job. Each resource section in the response represents a single past job request.
|
||||
|
||||
This example shows 1 historical job with a `completed` status. Since this was an unfiltered request, the job could either be archived, expired, or completed.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"entry": [
|
||||
{
|
||||
"resource": {
|
||||
"executionPeriod": {
|
||||
"end": "2026-08-14T00:07:48+00:00",
|
||||
"start": "2026-08-13T00:07:48+00:00"
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"system": "https://bcda.cms.gov/api/v3/jobs",
|
||||
"use": "official",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"input": [
|
||||
{
|
||||
"type": {
|
||||
"text": "BULK FHIR Export"
|
||||
},
|
||||
"valueString": "GET https://bcda.test.gov/this-is-a-test"
|
||||
}
|
||||
],
|
||||
"intent": "order",
|
||||
"resourceType": "Task",
|
||||
"status": "completed"
|
||||
}
|
||||
}
|
||||
],
|
||||
"resourceType": "Bundle",
|
||||
"total": 1,
|
||||
"type": "searchset"
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" %}
|
||||
|
||||
### Request attribution status
|
||||
|
||||
Check your attribution status for a timestamp of when your attribution data was last updated. By comparing the timestamp to the date of your most recent job, you can determine if your organization has new claims data to download.
|
||||
|
||||
#### Request to check attribution status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/attribution_status
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Request header
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
Authorization: Bearer {bearer_token}
|
||||
Accept: application/json
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="yaml" %}
|
||||
|
||||
#### curl command to check attribution status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/attribution_status" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Authorization: Bearer {bearer_token}"
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Response example
|
||||
If BCDA has never ingested an attribution or runout file for your organization, you'll receive a `404 not found` response.
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"ingestion_dates": [
|
||||
{
|
||||
"type": "last_attribution_update",
|
||||
"timestamp": "2020-12-22 22:31:40.397916+00"
|
||||
},
|
||||
{
|
||||
"type": "last_runout_update",
|
||||
"timestamp": "2020-12-22 22:31:40.397916+00"
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" %}
|
||||
|
||||
### Check API status
|
||||
|
||||
Retrieve metadata to view the current status and release or FHIR version of the API. A bearer token, and therefore a response header, is not required.
|
||||
|
||||
#### Request to check API status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/metadata
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### curl command to check API status
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl "https://sandbox.bcda.cms.gov/api/v3/metadata"
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Response example
|
||||
|
||||
The response will contain a FHIR Capability Statement resource in JSON format. The example below shows the API is active, using FHIR Release 4.0.1, and that the API release version is r292.
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"resourceType": "CapabilityStatement",
|
||||
"status": "active",
|
||||
"date": "2026-07-17T16:20:37Z",
|
||||
"publisher": "Centers for Medicare & Medicaid Services",
|
||||
"kind": "instance",
|
||||
"instantiates": [
|
||||
"https://prod-sbx.fhir.bfd.cmscloud.local/v3/fhir/metadata",
|
||||
"http://hl7.org/fhir/uv/bulkdata/CapabilityStatement/bulk-data"
|
||||
],
|
||||
"software": {
|
||||
"name": "Beneficiary Claims Data API",
|
||||
"version": "r292",
|
||||
"releaseDate": "2026-07-17T16:20:37Z"
|
||||
},
|
||||
"implementation": {
|
||||
"description": "The Beneficiary Claims Data API (BCDA) enables healthcare organizations participating in the Medicare Shared Savings Program or a CMS Innovation Model to retrieve Medicare Part A, Part B, and Part D claims data for their prospectively assigned or assignable beneficiaries.",
|
||||
"url": "https://sandbox.bcda.cms.gov"
|
||||
},
|
||||
"fhirVersion": "4.0.1",
|
||||
"format": [
|
||||
"application/json",
|
||||
"application/fhir+json"
|
||||
],
|
||||
"rest": [
|
||||
{
|
||||
"mode": "server",
|
||||
"security": {
|
||||
"cors": true,
|
||||
"service": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/restful-security-service",
|
||||
"code": "OAuth",
|
||||
"display": "OAuth"
|
||||
}
|
||||
],
|
||||
"text": "OAuth"
|
||||
}
|
||||
],
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris",
|
||||
"extension": [
|
||||
{
|
||||
"url": "token",
|
||||
"valueUri": "https://sandbox.bcda.cms.gov/auth/token"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"interaction": [
|
||||
{
|
||||
"code": "batch"
|
||||
},
|
||||
{
|
||||
"code": "search-system"
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"type": "Patient",
|
||||
"operation": [
|
||||
{
|
||||
"name": "export",
|
||||
"definition": "http://hl7.org/fhir/uv/bulkdata/OperationDefinition/patient-export",
|
||||
"documentation": "By default, the patient $export will return ExplanationOfBenefit resources with a meta.tag with a system of 'https://bluebutton.cms.gov/fhir/CodeSystem/System-Type' and code of either NationalClaimsHistory or DDPS. In order to return ExplanationOfBenefit resources with other system types (like SharedSystem), use the _typeFilter parameter."
|
||||
}
|
||||
],
|
||||
"searchParam": [
|
||||
{
|
||||
"name": "_since",
|
||||
"type": "date",
|
||||
"documentation": "Return resources updated after the date provided for existing and newly attributed enrollees."
|
||||
},
|
||||
{
|
||||
"name": "_type",
|
||||
"type": "string",
|
||||
"documentation": "Comma-delimited list of FHIR resource types to include in the export. By default, all supported resource types are returned."
|
||||
},
|
||||
{
|
||||
"name": "_typeFilter",
|
||||
"type": "string",
|
||||
"documentation": "Use a URL-encoded FHIR subquery to further-refine patient export results."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"operation": [
|
||||
{
|
||||
"name": "export",
|
||||
"definition": "http://hl7.org/fhir/uv/bulkdata/OperationDefinition/group-export",
|
||||
"documentation": "By default, the group $export will return ExplanationOfBenefit resources with a meta.tag with a system of 'https://bluebutton.cms.gov/fhir/CodeSystem/System-Type' and code of either NationalClaimsHistory or DDPS. In order to return ExplanationOfBenefit resources with other system types (like SharedSystem), use the _typeFilter parameter."
|
||||
}
|
||||
],
|
||||
"searchParam": [
|
||||
{
|
||||
"name": "_since",
|
||||
"type": "date",
|
||||
"documentation": "Return resources updated after the date provided for existing enrollees and all resources for newly attributed enrollees."
|
||||
},
|
||||
{
|
||||
"name": "_type",
|
||||
"type": "string",
|
||||
"documentation": "Comma-delimited list of FHIR resource types to include in the export. By default, all supported resource types are returned."
|
||||
},
|
||||
{
|
||||
"name": "_typeFilter",
|
||||
"type": "string",
|
||||
"documentation": "Use a URL-encoded FHIR subquery to further-refine group export results."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ExplanationOfBenefit",
|
||||
"searchParam": [
|
||||
{
|
||||
"name": "_tag",
|
||||
"type": "token",
|
||||
"documentation": "Filter ExplanationOfBenefit by the meta.tag element. Pass full token as <system>|<code>. Supported codes in the 'https://bluebutton.cms.gov/fhir/CodeSystem/System-Type' system are: 'SharedSystem', 'NationalClaimsHistory', and 'DDPS'. By Default, only NationalClaimsHistory and DDPS claims will be returned."
|
||||
},
|
||||
{
|
||||
"name": "outcome",
|
||||
"type": "token",
|
||||
"documentation": "Filter ExplanationOfBenefit by the outcome element. Supported values: 'partial' and 'complete'."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" %}
|
||||
240
dev/vendor/bcda-static-site/src/_pages/api-documentation/filter-claims-data.md
vendored
Normal file
240
dev/vendor/bcda-static-site/src/_pages/api-documentation/filter-claims-data.md
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
---
|
||||
layout: api-docs
|
||||
page_title: "How to Filter Claims Data"
|
||||
seo_title: ""
|
||||
description: "Learn how to filter Medicare claims data requests using the _since, _type, and _typeFilter parameters."
|
||||
in-page-nav: true
|
||||
feedback_id: "72cee024"
|
||||
tabs:
|
||||
- label: v3
|
||||
url: /api-documentation/filter-claims-data.html
|
||||
- label: v2
|
||||
url: /v2/api-documentation/filter-claims-data.html
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
{% include tabs.html items=page.tabs %}
|
||||
|
||||
BCDA offers 3 query parameters to filter or specify the resources returned:
|
||||
- **_since:** applies a date boundary to your request
|
||||
- **_type:** limits your request to 1 or more specific resource types
|
||||
- **_typeFilter:** limits your request to resources matching the provided criteria
|
||||
|
||||
Use parameters in the sandbox or production environment to speed up download times and reduce file size.
|
||||
|
||||
## The _since parameter
|
||||
|
||||
The _since parameter lets you filter for claims data last updated after a specified date. Dates must be in the [FHIR standard instant format](https://www.hl7.org/fhir/datatypes.html#instant): `YYYY-MM-DDThh:mm:ss.sss+zz:zz`.
|
||||
|
||||
- Sample date: July 1, 2026 12:00 PM EST
|
||||
- Formatted sample: `2026-07-01T12:00:00.000-05:00`
|
||||
|
||||
<div class="usa-alert usa-alert--warning usa-alert--no-icon">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text text-bold">Requesting data from before 03/26/2026</p>
|
||||
<p class="usa-alert__text">The FHIR resources available using BCDA v3 were first created in March 2026. Regardless of the service date, no resources from BCDA v3 will have a lastUpdated date earlier than March 2026.</p>
|
||||
<p>If you specify a date prior to 03/26/2026 with the _since parameter, your request will include a large amount of additional historical data for your enrollees. Your request using _since with a date on or later than 03/26/2026 will include resources marked with appropriate lastUpdated dates.</p>
|
||||
<p>If you wish to filter claims based on the date of service, use the _typeFilter parameter with service-date in your v3 request.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
We recommend new model entities run an unfiltered request for all historical data before using _since subsequently for incremental exports of new data. You can use the transactionTime from your most recent job as the specified date.
|
||||
|
||||
The _since parameter can be used with /Group or /Patient, but each endpoint will return data for newly attributed enrollees differently.
|
||||
|
||||
### Using _since with /Patient
|
||||
|
||||
Using _since with /Patient will return resources updated after the date provided for existing and newly attributed enrollees.
|
||||
|
||||
Newly attributed enrollees are those who’ve been assigned to your model entity since your last attribution date. If you don’t apply _since, BCDA will return data as early as March 1, 2021.
|
||||
|
||||
#### Example request using _since with /Patient
|
||||
|
||||
<!-- snippet -->
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Patient/$export?_type=Patient&_since=2026-07-01T08:00:00.000-05:00
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Example curl command using _since with /Patient
|
||||
|
||||
This command combines the `GET` request and request header. The dollar sign `$` before `export` in the URL indicates the endpoint is an action, not a resource.
|
||||
|
||||
<!-- snippet -->
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Patient/\$export?_type=Patient&_since=2026-07-01T08:00:00.000-05:00" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
### Using _since with /Group
|
||||
|
||||
Using _since with /Group will return resources updated after the date provided for existing enrollees and all resources for newly attributed enrollees.
|
||||
|
||||
This lets you retrieve all new claims data with a single request. If you don't apply _since, BCDA will return data as early as March 1, 2021.
|
||||
|
||||
#### Example request using _since with /Group
|
||||
|
||||
The request below will return:
|
||||
|
||||
- Claims data last updated since July 1, 2026 for existing enrollees
|
||||
- Claims data as far back as 2014 for all new enrollees attributed to your organization in the last month
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/all/$export?_type=Patient&_since=2026-07-01T08:00:00.000-05:00
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Example curl command using _since with /Group
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export?_type=Patient&_since=2026-07-01T08:00:00.000-05:00" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
## The _type parameter
|
||||
|
||||
The _type parameter lets you specify which resource types you'd like returned. Otherwise, making a `GET` request to start a job with the /Group or /Patient endpoint will return all resource types.
|
||||
|
||||
### Using _type
|
||||
|
||||
You'll need to use commas when specifying multiple resource types. The examples below are curl requests to /Group using _type.
|
||||
|
||||
#### Example request for 1 resource type
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/all/$export?_type=Patient
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Example request for 2 resource types
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/all/$export?_type=ExplanationOfBenefit,Patient
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Example curl command using _type
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export?_type=ExplanationOfBenefit,Patient" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
## The _typeFilter parameter
|
||||
|
||||
<div class="usa-alert usa-alert--success usa-alert--no-icon">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text text-bold font-ui-lg">Only available in v3</p>
|
||||
<p class="usa-alert__text">The _typeFilter parameter is only available in v3.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The _typeFilter parameter lets you use resource metadata to create finer-grained filtering criteria. BCDA will only return claims data that meets the specified criteria provided as a URL Encoded FHIR REST API query.
|
||||
|
||||
### Using _typeFilter
|
||||
|
||||
Currently, _typeFilter supports queries using the `_tag`, `outcome`, and `service-date` search parameters for the ExplanationOfBenefit resource:
|
||||
|
||||
* **_tag:** filters the export based on the `meta.tag` element. Pass a full token as `<system>|<code>`.
|
||||
* Supported codes in the `https://bluebutton.cms.gov/fhir/CodeSystem/System-Type` system are:
|
||||
* `SharedSystem`
|
||||
* `NationalClaimsHistory`
|
||||
* `DDPS`
|
||||
* If you do not specify a System-Type _tag filter, the export will include only `NationalClaimsHistory` and `DDPS` claims by default.
|
||||
|
||||
* **outcome:** filters the export based on the processing status of the claim. The two values for this param, `partial` and `complete`, represent the "Partially Adjudicated" and "Fully Adjudicated" claim statuses.
|
||||
|
||||
* **service-date:** filters the export based on the claim's service date. The service date is the date that the care occurred within a billable period. This is a FHIR date param format (ex. `gt2026-01-14`)
|
||||
|
||||
|
||||
#### Example request using _typeFilter
|
||||
|
||||
The request below will return:
|
||||
- All Patient and Coverage resources
|
||||
- ExplanationOfBenefit resources of fully adjudicated claims only
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/all/$export?_typeFilter=ExplanationOfBenefit%3Foutcome%3Dcomplete
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
#### Example curl command using _typeFilter
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/all/\$export?_typeFilter=ExplanationOfBenefit%3Foutcome%3Dcomplete" \
|
||||
-H "Accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
## The \`runout` identifier
|
||||
|
||||
The \`runout` identifier lets you request runouts data for enrollees attributed to your model entity the previous year, but not the current year. Claims data returned will have a service date no later than December 31 of the previous year.
|
||||
|
||||
### Using the `runout` identifier
|
||||
|
||||
The examples below are `GET` requests made to the /Group endpoint.
|
||||
|
||||
#### Example request for 1 resource type using `runout`
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/runout/$export?_type=Patient
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/runout/\$export?_type=Patient" \
|
||||
-H "accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Example request for 2 resource types using `runout`
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/runout/$export?_type=ExplanationOfBenefit,Patient
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/runout/\$export?_type=ExplanationOfBenefit,Patient" \
|
||||
-H "accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
|
||||
#### Request for all resources using `runout`
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
GET /api/v3/Group/runout/$export
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" %}
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
curl -X GET "https://sandbox.bcda.cms.gov/api/v3/Group/runout/\$export" \
|
||||
-H "accept: application/fhir+json" \
|
||||
-H "Prefer: respond-async" \
|
||||
-H "Authorization: Bearer {bearer_token}" \
|
||||
-i
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="shell" can_copy=true %}
|
||||
345
dev/vendor/bcda-static-site/src/_pages/api-documentation/how-to-migrate-v3.md
vendored
Normal file
345
dev/vendor/bcda-static-site/src/_pages/api-documentation/how-to-migrate-v3.md
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
---
|
||||
layout: api-docs
|
||||
page_title: "How to Migrate to v3"
|
||||
seo_title: "How to Migrate to BCDA v3 | CMS Beneficiary Claims Data API"
|
||||
description: "Learn about new endpoints, FHIR resource consolidation, and updated URLs and IDs."
|
||||
in-page-nav: true
|
||||
feedback_id: "8a4c0b7b"
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
BCDA v3 became available July 1, 2026. Existing v1 and v2 users will need to migrate to v3 by July 30, 2027.
|
||||
|
||||
Along with background information, this guide:
|
||||
|
||||
- Introduces the [v3 Data Dictionary]({{ '/assets/downloads/BCDA_v3_Data_Dictionary.xlsx' | relative_url }})
|
||||
- Helps you reformat [requests](#requesting-data-from-new-v3-endpoints) and [URLs](#new-extension-and-code-system-urls) in v3
|
||||
- Introduces the [_typeFilter]({{ '/api-documentation/filter-claims-data.html#the-_typefilter-parameter' | relative_url }}) parameter
|
||||
- Provides additional info on mapping and matching claims and variables between versions
|
||||
|
||||
## Migration overview
|
||||
|
||||
As you migrate from BCDA v1/v2 to v3, you’ll need to become familiar with changes including:
|
||||
|
||||
- New endpoint URL structure
|
||||
- Unified ExplanationOfBenefit resource for all claims
|
||||
- Updated extension and code system URLs
|
||||
- Different resource IDs requiring new matching strategies
|
||||
|
||||
Please note that your initial v3 data request may take extra time. Additionally, as we continue to refine the v3 dataset, some fields will occasionally be re-loaded on our end. Corrected claims will show a more recent `last_updated` date and may result in larger than expected payloads. We will provide updates when these reloads are expected.
|
||||
|
||||
## BCDA v3 overview
|
||||
|
||||
BCDA v3 introduces access to more timely and accurate Medicare claims data and additional benefits to BCDA users including:
|
||||
|
||||
- More consistent claims data by using a single data source, the CMS Integrated Data Repository.
|
||||
- Improved alignment with sources such as the Claim and Claim Line Feed (CCLF) files through more standard claim and patient identifiers.
|
||||
|
||||
### Changes to BCDA in v3
|
||||
|
||||
If you are currently using BCDA v1 or BCDA v2, there are changes to the API and FHIR Resources you need to be aware of in v3:
|
||||
|
||||
- There is a new v3 endpoint: `https://api.bcda.cms.gov/api/v3/`
|
||||
- Partially adjudicated claims<sup><a href="#fn1">1</a></sup> will be represented as `ExplanationOfBenefit` (EOB) FHIR resources in v3, instead of `Claim`/`ClaimResponse` in v2
|
||||
- There will be new Extension and Code System URLs to reference
|
||||
- Resource IDs are different between versions
|
||||
|
||||
<p id="fn1" style="scroll-margin-top: 6.25rem;"><sup>1</sup> Medical claims that have been submitted but not fully processed and paid by Medicare.</p>
|
||||
|
||||
### v3 Data Dictionary
|
||||
|
||||
Download the <a href="{{ '/assets/downloads/BCDA_v3_Data_Dictionary.xlsx' | relative_url }}" data-tealium="download">BCDA v3 Data Dictionary</a> to learn about:
|
||||
|
||||
- updated information on resource type and claim field names
|
||||
- updated mappings between CCLF and BCDA data
|
||||
- new data available in v3
|
||||
|
||||
## Requesting data from new v3 endpoints
|
||||
|
||||
### Point your app to v3 instead of v2
|
||||
|
||||
BCDA v3 will be located at the same domain as v1 and v2. However, it will have new endpoints for metadata requests and the Group and Patient $export operations. BCDA v3 will still support the [Bulk Data FHIR Implementation Guide](https://hl7.org/fhir/uv/bulkdata/STU2/). If you are currently connected to BCDA v1 or v2 endpoints, you can migrate to BCDA v3 by making the following changes.
|
||||
|
||||
Point your app to **v3** instead of **v2** to connect to the new endpoint, complete the steps to export the data, check the job status, read the JSON manifest, and download the files.
|
||||
|
||||
- v2 Patient $export operation:
|
||||
{% include copy_snippet.html code="GET /api/v2/Group/all/$export" language="shell" %}
|
||||
|
||||
- v3 Patient $export operation:
|
||||
{% include copy_snippet.html code="GET /api/v3/Group/all/$export" language="shell" %}
|
||||
|
||||
The /auth and /data URLs will remain the same between versions. You won't need to make any changes to how you request a token or download files.
|
||||
|
||||
## Changes to claims representation
|
||||
|
||||
In BCDA v3, claims data are still returned in FHIR R4 format, but there are changes to the Patient, Coverage, and ExplanationOfBenefit resources. These changes include:
|
||||
- Conformity with [CARIN Blue Button Implementation Guide (v2.2.0)](https://hl7.org/fhir/us/carin-bb/STU2.2/en/)
|
||||
- New StructureDefenition and CodeSystem URLs for CMS-specific extensions and terminologies
|
||||
- New data elements
|
||||
|
||||
Refer to the [v3 Data Dictionary]({{ '/api-documentation/how-to-migrate-v3.html#v3-data-dictionary' | relative_url }}) for a list of v3 supported data elements.
|
||||
|
||||
### Changes to partially adjudicated claims
|
||||
|
||||
BCDA v2 represents fully adjudicated claims by the ExplanationOfBenefit FHIR resource and partially adjudicated claims by the Claim and ClaimResponse resources.
|
||||
|
||||
BCDA v3 represents all claims (fully and partially adjudicated) with the ExplanationOfBenefit resource.
|
||||
|
||||
- v2 - `Claim`/`ClaimResponse` resource
|
||||
- v3 - `ExplanationOfBenefit` resource
|
||||
|
||||
This change in v3 lets you compare claims at different stages in the adjudication process without mapping between resource types.
|
||||
|
||||
### Exporting claims based on adjudication status with v3
|
||||
|
||||
If you make requests for `Claim` and/or `ClaimResponse` resources using the `_type` parameter in v2, you'll need to update those requests in v3.
|
||||
|
||||
#### How it works in v2
|
||||
|
||||
In BCDA v2 you exported claims based on adjudication status using the `_type` parameter, and passing the appropriate resource types (`Claim`, `ClaimResponse` for partially adjudicated claims and `ExplanationOfBenefit` for fully adjudicated claims) as the parameter value:
|
||||
|
||||
- v2 $export for only partially adjudicated claims:
|
||||
{% include copy_snippet.html code="GET /api/v2/Patient/$export?_type=Claim,ClaimResponse" language="shell" %}
|
||||
|
||||
- v2 $export for only fully adjudicated claims:
|
||||
{% include copy_snippet.html code="GET /api/v2/Patient/$export?_type=ExplanationOfBenefit" language="shell" %}
|
||||
|
||||
In v2, BCDA differentiates "partially adjudicated" from "fully adjudicated" claims based on the data source rather than the claim status. BCDA's partially adjudicated data (Claim and ClaimResponse resources) are populated with data from the Medicare Shared Systems which often includes data for claims that had been fully processed and paid.
|
||||
|
||||
#### How it works in v3
|
||||
|
||||
We've extended the API with the [`_typeFilter` parameter]({{ '/api-documentation/filter-claims-data.html#the-_typefilter-parameter' | relative_url }}) to filter export data more granularly. Because all claims in v3 are represented by the same resource type (`ExplanationOfBenefit`), use this parameter to specify the System-Type _tag, recreating your v2 filtering logic.
|
||||
|
||||
Remember when using the _typeFilter parameter:
|
||||
1. The _typeFilter parameter value must be URL-encoded
|
||||
2. The _tag subquery parameter requires a token in the form `system|code`
|
||||
|
||||
{% capture sampleRequest %}{% raw %}
|
||||
GET /api/v3/Patient/$export
|
||||
?_type=ExplanationOfBenefit
|
||||
&_typeFilter=ExplanationOfBenefit%3F_tag%3Dhttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CSharedSystem
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=sampleRequest language="shell" %}
|
||||
|
||||
**If you received `ExplanationOfBenefit`, `Claim`, and `ClaimResponse` resources in v2,** you received all claims.
|
||||
|
||||
In v3, specify all System-Type codes:
|
||||
- SharedSystem
|
||||
- NationalClaimsHistory
|
||||
- DDPS
|
||||
|
||||
{% capture sampleRequest %}{% raw %}
|
||||
GET /api/v3/Patient/$export
|
||||
?_type=ExplanationOfBenefit
|
||||
&_typeFilter=ExplanationOfBenefit%3F_tag%3Dhttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CSharedSystem%2Chttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CNationalClaimsHistory%2Chttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CDDPS
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=sampleRequest language="shell" %}
|
||||
|
||||
**If you used the _type parameter for `_type=Claim,ClaimResponse` in v2 requests,** you received SharedSystem claims only.
|
||||
|
||||
In v3, specify the SharedSystem System-Type code:
|
||||
{% capture sampleRequest %}{% raw %}
|
||||
GET /api/v3/Patient/$export
|
||||
?_type=ExplanationOfBenefit
|
||||
&_typeFilter=ExplanationOfBenefit%3F_tag%3Dhttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CSharedSystem
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=sampleRequest language="shell" %}
|
||||
|
||||
**If you used the _type parameter for `_type=ExplanationOfBenefit` for v2 requests,** you received NationalClaimsHistory claims and DDPS claims.
|
||||
|
||||
In v3, specify the NationalClaimsHistory and DDPS System-Type codes:
|
||||
{% capture sampleRequest %}{% raw %}
|
||||
GET /api/v3/Patient/$export
|
||||
?_type=ExplanationOfBenefit
|
||||
&_typeFilter=ExplanationOfBenefit%3F_tag%3Dhttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CNationalClaimsHistory%2Chttps%3A%2F%2Fbluebutton.cms.gov%2Ffhir%2FCodeSystem%2FSystem-Type%7CDDPS
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=sampleRequest language="shell" %}
|
||||
|
||||
##### Omitting _typeFilter
|
||||
|
||||
In v3, if you make a request without using the `_typeFilter` parameter to filter by System-Type, BCDA will return only NationalClaimsHistory and DDPS claims.
|
||||
|
||||
{% capture sampleRequest %}{% raw %}
|
||||
GET /api/v3/Patient/$export?_type=ExplanationOfBenefit
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=sampleRequest language="shell" %}
|
||||
|
||||
## New extension and code system URLs
|
||||
|
||||
BCDA v3 uses the StructureDefinition and CodeSystem FHIR resources. This is the standard way to represent code system URLs. It makes it easier to access the metadata. You can access the metadata for each Extension and CodeSystem by hitting the URL. This will return a StructureDefinition or CodeSystem FHIR resource.
|
||||
|
||||
### URLs changing between v2 and v3
|
||||
|
||||
Make the following changes to migrate to v3 if you’ve mapped any bluebutton extension or code system URLs:
|
||||
|
||||
#### Changes to Extension URLs
|
||||
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked margin-bottom-4">
|
||||
<caption class="usa-sr-only">Changes to Extension URLs between v2 and v3</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">v2 URL</th>
|
||||
<td>
|
||||
{% include copy_snippet.html code="https://bluebutton.cms.gov/resources/variables/nch_near_line_rec_ident_cd/" language="ruby" can_copy=true %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">v3 URL</th>
|
||||
<td>
|
||||
{% include copy_snippet.html code="https://bluebutton.cms.gov/fhir/StructureDefinition/CLM-NRLN-RIC-CD" language="ruby" can_copy=true %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
#### Changes to CodeSystem URLs
|
||||
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked margin-bottom-4">
|
||||
<caption class="usa-sr-only">Changes to CodeSystem URLs between v2 and v3</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">v2 URL</th>
|
||||
<td>
|
||||
{% include copy_snippet.html code="https://bluebutton.cms.gov/resources/variables/nch_near_line_rec_ident_cd/" language="ruby" can_copy=true %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">v3 URL</th>
|
||||
<td>
|
||||
{% include copy_snippet.html code="https://bluebutton.cms.gov/fhir/CodeSystem/CLM-NRLN-RIC-CD" language="ruby" can_copy=true %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
If your BCDA client is using any of the v2 URLs, you’ll need to update your code to look for the v3 version.
|
||||
|
||||
## Different resource IDs between versions
|
||||
|
||||
<div class="usa-alert usa-alert--warning">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__heading text-bold">Do not use FHIR IDs to match resources between versions.</p>
|
||||
<p class="usa-alert__text">To match <a href="{{ '/api-documentation/how-to-migrate-v3.html#matching-beneficiaries-between-v2-and-v3' | relative_url }}">beneficiaries</a>, use MBI and demographics data. To match <a href="{{ '/api-documentation/how-to-migrate-v3.html#matching-claims-between-v2-and-v3' | relative_url }}">claims</a>, use the claim control number.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
In BCDA v3, the FHIR ID (`Patient.id`, `ExplanationOfBenefit.id`, `Coverage.id`) for a given resource will not match the v2 FHIR resource ID.
|
||||
|
||||
**Example:**
|
||||
|
||||
If you receive the `Patient` resource from BCDA v3, do not look up an existing patient by expecting the v2 `Patient.id` to equal the v3 `Patient.id` value. These values will not match. They are a different set of identifiers. However, the range of v2 and v3 Patient IDs does overlap, so there are some IDs that represent a v2 Patient and v3 Patient. This would not mean they represent the same beneficiary in real life.
|
||||
|
||||
Instead of matching on `Patient.id`, use the relevant CMS identifier (MBI), along with additional supporting resource info (patient demographics like name, date of birth, etc.). Similarly, `ExplanationOfBenefit.id` and Coverage.id will not match between v2 and v3.
|
||||
|
||||
### Matching beneficiaries between v2 and v3
|
||||
|
||||
Because the v2 and v3 Patient IDs will not match, in order to match a v3 patient to a patient you already have in your database, the best way is to use the MBI and demographics. The MBI and demographics should match between versions of BCDA.
|
||||
|
||||
To find the MBI in the FHIR Patient resource, look at the `Patient.identifier` element for an identifier where the `identifier.system` is `http://hl7.org/fhir/sid/us-mbi`. The `identifier.value` will be the MBI.
|
||||
|
||||
Example v3 `Patient.identifier` element:
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-mbi",
|
||||
"value": "1S00E00AA08",
|
||||
"period": {
|
||||
"start": "2018-01-24T00:00:00+00:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" can_copy=true %}
|
||||
|
||||
It is always good practice to have some kind of validation based on patient demographics, like supplementing the MBI exact match with some subset of name, date of birth, and gender matching.
|
||||
|
||||
Once you have matched an existing `Patient`, or created a new one, it is ok to store the BCDA v3 `Patient.id`. It is possible for two different MBIs to reference the same beneficiary, and therefore the same BCDA v3 `Patient`. If that happens, you will see multiple instances of `Patient.identifier` where the system is `http://hl7.org/fhir/sid/us-mbi`. This is rare, but you should be prepared for it to happen.
|
||||
|
||||
Example of v3 `Patient.identifier` with two MBIs:
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-mbi",
|
||||
"value": "1S00E00AA08",
|
||||
"period": {
|
||||
"start": "2022-04-05T00:00:00+00:00",
|
||||
"end": "2022-04-12T00:00:00+00:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-mbi",
|
||||
"value": "1S00E00AE22",
|
||||
"period": {
|
||||
"start": "2022-04-12T00:00:00+00:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" can_copy=true %}
|
||||
|
||||
### Matching claims between v2 and v3
|
||||
|
||||
Because the v3 `ExplanationOfBenefit` FHIR ID will not match the v2 `ExplanationOfBenefit` or `Claim`/`ClaimResponse` FHIR IDs, in order to match a v3 claim to a claim you already have in your database, the best way is to use the claim control number.
|
||||
|
||||
Because each version of a claim will have a unique claim ID, you should avoid using the unique claim ID to track a claim across versions, or even within a version across the adjudication journey. Instead, use the claim control number. It is the identifier where the `identifier.system` equals `https://bluebutton.cms.gov/identifiers/CLM-CNTL-NUM`.
|
||||
|
||||
Example `ExplanationOfBenefit.identifier` element:
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "uc",
|
||||
"display": "Unique Claim ID"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": "-9422919134190"
|
||||
},
|
||||
{
|
||||
"system": "https://bluebutton.cms.gov/identifiers/CLM-CNTL-NUM",
|
||||
"value": "05968201612271KJS"
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" can_copy=true %}
|
||||
|
||||
FHIRPath for claim identifier:
|
||||
|
||||
{% include copy_snippet.html code="ExplanationOfBenefit.identifier.where(system = 'https://bluebutton.cms.gov/identifiers/CLM-CNTL-NUM').value" language="ruby" can_copy=true %}
|
||||
218
dev/vendor/bcda-static-site/src/_pages/bcda-data/comparison-bcda-cclf-files.md
vendored
Normal file
218
dev/vendor/bcda-static-site/src/_pages/bcda-data/comparison-bcda-cclf-files.md
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
---
|
||||
layout: api-docs
|
||||
page_title: "Comparison of BCDA v3 and CCLF Files"
|
||||
seo_title: ""
|
||||
description: ""
|
||||
in-page-nav: true
|
||||
tabs:
|
||||
- label: v3
|
||||
url: /bcda-data/comparison-bcda-cclf-files.html
|
||||
- label: v2
|
||||
url: /v2/bcda-data/comparison-bcda-cclf-files.html
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
{% include tabs.html items=page.tabs %}
|
||||
|
||||
Beneficiary Claims Data API (BCDA) and [Claim and Claim Line Feed (CCLF)](https://www.cms.gov/files/document/cclf-information-packet.pdf) files both offer Medicare Parts A, B, and D claims data. While the data sources overlap, there are some differences including their formatting and the frequency with which they make data available.
|
||||
|
||||
## Overview of similarities and differences
|
||||
|
||||
BCDA and CCLF files differ in how and when Medicare claims data is made available.
|
||||
|
||||
### BCDA: frequent access and standardization
|
||||
|
||||
As an API, BCDA lets you automate data requests and streamline workflows for more efficient, secure processes. The API uses Fast Healthcare Interoperability Resources ([FHIR](https://www.hl7.org/fhir/)) standards. It also supports frequent and early access to Medicare enrollees' claims data. This includes access to claims that would otherwise be unavailable during processing ("adjudication"). Earlier access to claims helps your organization build near real-time workflows and coordinate care for your patients.
|
||||
|
||||
FHIR breaks claims data into discrete blocks of recognizable data called `Resources`. These use information categories such as Patient, Explanation of Benefit, and Coverage to organize files, which make them easy to share and interpret. BCDA uses the [CMS recommended Bulk FHIR Standards](https://www.cms.gov/priorities/key-initiatives/burden-reduction/interoperability/implementation-guides-and-standards/standards-and-igs-index-and-resources) to export larger amounts of data across systems and populations.
|
||||
|
||||
### CCLF: ease of access and direct delivery
|
||||
|
||||
CCLF files provide adjudicated claims data delivered on a regular cadence, which may be useful for organizations with established file-based workflows or analyses that depend on fields unavailable in BCDA. You can download CCLF files directly from your model-specific portal.
|
||||
|
||||
## Who can use BCDA and CCLF files?
|
||||
|
||||
BCDA and CCLF claims data are available to organizations participating in some [Alternative Payment Models](https://www.cms.gov/priorities/innovation/key-concepts/alternative-payment-models-apms). These are programs that reward health care providers for high-quality, coordinated care. BCDA and CCLF files are currently available to organizations in these models:
|
||||
|
||||
- Medicare Shared Savings Program
|
||||
- Realizing Equity, Access, and Community Health (ACO REACH)
|
||||
- Kidney Care Choices (KCC)
|
||||
- Increasing Organ Transplant Access (IOTA)
|
||||
|
||||
## Differences in access, format, content, and timing
|
||||
|
||||
{% assign unique = 1 %}
|
||||
<table class="usa-table usa-table--stacked usa-table--borderless margin-bottom-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="col-bcda-{{unique}}">BCDA</th>
|
||||
<th scope="col" id="col-cclf-{{unique}}">CCLF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="2" id="colgroup-{{unique}}">Access method</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td headers="col-bcda-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">Programmatically using API requests</td>
|
||||
<td headers="col-cclf-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">Manually using portals or programmatically using API requests or Command Line Interface</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% assign unique = 2 %}
|
||||
<table class="usa-table usa-table--stacked usa-table--borderless margin-y-0">
|
||||
<thead class="usa-sr-only">
|
||||
<tr>
|
||||
<th scope="col" id="col-bcda-{{unique}}">BCDA</th>
|
||||
<th scope="col" id="col-cclf-{{unique}}">CCLF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="2" id="colgroup-{{unique}}">Claims data format</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td headers="col-bcda-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">Interoperable and machine-readable, using <a href="https://hl7.org/fhir/R4/">FHIR R4</a>.</td>
|
||||
<td headers="col-cclf-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">Human-readable, using <a href="https://www.cms.gov/files/document/cclf-information-packet.pdf">fixed-width tabular files</a>.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% assign unique = 3 %}
|
||||
<table class="usa-table usa-table--stacked usa-table--borderless margin-y-0">
|
||||
<thead class="usa-sr-only">
|
||||
<tr>
|
||||
<th scope="col" id="col-bcda-{{unique}}">BCDA</th>
|
||||
<th scope="col" id="col-cclf-{{unique}}">CCLF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="2" id="colgroup-{{unique}}">Claims data source</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td headers="col-bcda-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">National Claims History and CMS Shared Systems data from the Integrated Data Repository (IDR)</td>
|
||||
<td headers="col-cclf-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">National Claims History data from the Integrated Data Repository (IDR)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% assign unique = 4 %}
|
||||
<table class="usa-table usa-table--stacked usa-table--borderless margin-y-0">
|
||||
<thead class="usa-sr-only">
|
||||
<tr>
|
||||
<th scope="col" id="col-bcda-{{unique}}">BCDA</th>
|
||||
<th scope="col" id="col-cclf-{{unique}}">CCLF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="2" id="colgroup-{{unique}}">Historical data provided on newly attributed enrollees</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td headers="col-bcda-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">
|
||||
<ul style="margin: 0; padding-left: 1rem;">
|
||||
<li>Shared Savings Program: all historical data available as far back as 2014</li>
|
||||
<li>ACO REACH: 36 months of historical data from the start of the current performance year</li>
|
||||
<li>KCC: 24 months of historical data from the start of the current performance year</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td headers="col-cclf-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">
|
||||
<ul style="margin: 0; padding-left: 1rem;">
|
||||
<li>Shared Savings Program: 36 months prior to agreement start date</li>
|
||||
<li>ACO REACH: 36 months of historical data from the start of the current performance year</li>
|
||||
<li>KCC: 36 months of historical data from the start of the current performance year</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% assign unique = 5 %}
|
||||
<table class="usa-table usa-table--stacked usa-table--borderless margin-y-0">
|
||||
<thead class="usa-sr-only">
|
||||
<tr>
|
||||
<th scope="col" id="col-bcda-{{unique}}">BCDA</th>
|
||||
<th scope="col" id="col-cclf-{{unique}}">CCLF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="2" id="colgroup-{{unique}}">Update frequency</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td headers="col-bcda-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">
|
||||
<ul style="margin: 0; padding-left: 1rem;">
|
||||
<li>Adjudicated claims (Medicare Part A and Part B): weekly</li>
|
||||
<li>Adjudicated claims (Medicare Part D): 6x/week</li>
|
||||
<li>Partially adjudicated claims: 4 to 5x/week</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td headers="col-cclf-{{unique}} colgroup-{{unique}}" style="vertical-align: baseline; padding: 1rem; width:50%; border-bottom:0;">
|
||||
<ul style="margin: 0; padding-left: 1rem;">
|
||||
<li>Adjudicated claims: monthly</li>
|
||||
<li>Adjudicated claim extracts: weekly<sup><a href="#fn1">*</a></sup></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul>
|
||||
<li id="fn1" style="scroll-margin-top: 6.25rem;">CCLF Self-Service is only available to ACOs in the Medicare Shared Savings Program. Visit the <a href="https://acoms.cms.gov/knowledge-management/view/8321">ACO-MS Knowledge Library</a> to learn more about CCLF Self-Service.</li>
|
||||
</ul>
|
||||
|
||||
## Which data source is right for me?
|
||||
|
||||
Both sources support model entities' use cases, and can even be used together. When choosing between them, consider the frequency, source, and level of automation most suited to your needs. You can also consider your organization's existing healthcare analytics and actuarial tools. A benefit of BCDA is its conformance with FHIR standards. This saves time and money on integrations with EHRs and off-the-shelf FHIR data clients.
|
||||
|
||||
BCDA aligns with CCLF partly by using shared claim identifiers. This alignment makes it easy to use both CCLF files and BCDA's standards-based format.
|
||||
|
||||
### Can I use both CCLF and BCDA as sources of Medicare claims data?
|
||||
|
||||
Yes. Using both data sources provides many benefits:
|
||||
|
||||
- Ensure data accuracy: Compare and cross-reference both sources to access a wider variety of historical data and identify any discrepancies.
|
||||
- Evaluate care coordination and payment processes: Combine BCDA's insurance coverage details with CCLF files' payment data. This can evaluate the coordination of benefits between Medicare and other insurers, assess the financial impact on all parties, and identify primary or secondary payers.
|
||||
|
||||
## How soon will I receive claims data from BCDA v3 and CCLF files?
|
||||
|
||||
Depending on the model your organization participates in, claims data can be available:
|
||||
|
||||
- 2-4 days after the claim is **submitted to CMS** via BCDA v3
|
||||
- 1-7 days after the claim is **adjudicated by CMS** via CCLF
|
||||
|
||||
The following diagram illustrates these differences while providing more detail about the claims process.
|
||||
|
||||
<figure class="width-full margin-y-4 margin-x-0" style="border: 1px solid #f0f0f0; border-radius: 4px; overflow:hidden; padding: 12px;">
|
||||
<img
|
||||
src="{{ '/assets/img/bcda-vs-cclf.svg' | relative_url }}"
|
||||
alt="Timeline of when claim data becomes available to users through BCDA and CCLF after providers submit a claim."
|
||||
class="width-full"
|
||||
>
|
||||
<figcaption class="usa-sr-only">
|
||||
<ul>
|
||||
<li>Through BCDA: a partially adjudicated claim is available 2 to 4 days after submission and continues to update every 2 to 4 days while CMS is processing. Once the claim is fully adjudicated, it is available through BCDA within 1 to 7 days.</li>
|
||||
<li>Through CCLF: the fully adjudicated claim is available via CCLF Self-Service 1 to 7 days after adjudication and via the CCLF monthly feed 2 to 6 weeks after full adjudication.</li>
|
||||
</ul>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## Which data fields in BCDA v3 are not available from CCLF files?
|
||||
|
||||
The BCDA v3 Data Dictionary contains a comprehensive list of all data fields available in BCDA v3 and includes a mapping to the equivalent or related CCLF file and element.
|
||||
|
||||
<p>For an exhaustive list of fields available only in BCDA v3, look for rows where the <code class="language-plaintext highlighter-rouge">cclfMapping</code> column is blank in the "Data Dictionary" worksheet in the <a href="{{ '/assets/downloads/BCDA_v3_Data_Dictionary.xlsx' | relative_url }}" data-tealium="download">BCDA v3 Data Dictionary {% include sprite.html icon="file_download" class="text-middle" size="2" %}</a>.</p>
|
||||
|
||||
## Which data fields in CCLF files are not available from BCDA v3?
|
||||
|
||||
The majority of data elements found in CCLF files are also available from BCDA v3. There are some elements not available from BCDA v3 including:
|
||||
|
||||
- Legacy HICN identifiers
|
||||
- Certain beneficiary demographic information
|
||||
- CCLF metadata
|
||||
- Data fields from inactive programs
|
||||
|
||||
Review the "CCLF fields absent from BCDA v3" worksheet in the BCDA v3 Data Dictionary for a comprehensive list of fields found in CCLF files which are unavailable in BCDA v3.
|
||||
231
dev/vendor/bcda-static-site/src/_pages/bcda-data/partially-adjudicated-claims-data.md
vendored
Normal file
231
dev/vendor/bcda-static-site/src/_pages/bcda-data/partially-adjudicated-claims-data.md
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
layout: api-docs
|
||||
page_title: "Partially Adjudicated Claims Data"
|
||||
seo_title: ""
|
||||
description: "Get earlier access to Medicare enrollee data with BCDA partially adjudicated claims, now available as ExplanationOfBenefit resources in v3."
|
||||
in-page-nav: true
|
||||
feedback_id: "355f6ea6"
|
||||
tabs:
|
||||
- label: v3
|
||||
url: /bcda-data/partially-adjudicated-claims-data.html
|
||||
- label: v2
|
||||
url: /v2/bcda-data/partially-adjudicated-claims-data.html
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
{% include tabs.html items=page.tabs %}
|
||||
|
||||
<p class="usa-intro">A faster way to access claims data</p>
|
||||
|
||||
Medicare claims processing (adjudication) can take weeks. Through partially adjudicated claims, ACO REACH participants (REACH ACOs) receive patient data only 2-4 days after providers submit claims to Medicare. This helps them provide patients with more effective interventions, transitions, and coordination of care.
|
||||
|
||||
Fully adjudicated claims offer the same rich insights, with some small differences including a longer frequency.
|
||||
|
||||
## Comparison: partially and fully adjudicated claims
|
||||
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked margin-bottom-4">
|
||||
<caption class="usa-sr-only">Partially versus fully adjudicated claims data</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Partially adjudicated claims</th>
|
||||
<th scope="col">Fully adjudicated claims</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr scope="row">
|
||||
<td data-label="Partially adjudicated claims">
|
||||
Update daily
|
||||
</td>
|
||||
<td data-label="Fully adjudicated claims">
|
||||
Update weekly
|
||||
</td>
|
||||
</tr>
|
||||
<tr scope="row">
|
||||
<td data-label="Partially adjudicated claims">
|
||||
Available to all <a href="{{ '/index.html#eligible-model-entities' | relative_url }}">eligible model
|
||||
entities</a>, except <a
|
||||
href="https://www.cms.gov/priorities/innovation/innovation-models/guide"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>GUIDE</a> participants
|
||||
</td>
|
||||
<td data-label="Fully adjudicated claims">
|
||||
Available to all <a href="{{ '/index.html#eligible-model-entities' | relative_url }}">eligible model
|
||||
entities</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr scope="row">
|
||||
<td data-label="Partially adjudicated claims">
|
||||
Requires BCDA v2<sup><a href="#fn1">*</a></sup> or v3
|
||||
</td>
|
||||
<td data-label="Fully adjudicated claims">
|
||||
Available in all BCDA versions
|
||||
</td>
|
||||
</tr>
|
||||
<tr scope="row">
|
||||
<td data-label="Partially adjudicated claims">
|
||||
Sourced from Medicare Shared Systems
|
||||
</td>
|
||||
<td data-label="Fully adjudicated claims">
|
||||
Sourced from National Claims History (NCH)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul>
|
||||
<li id="fn1" style="scroll-margin-top: 6.25rem;">Access to partially adjudicated claims data using BCDA v2 is limited to REACH ACOs. Participants in other CMS Innovation Models and the Medicare Shared Savings Program must use BCDA v3 to access partially adjudicated claims data.</li>
|
||||
</ul>
|
||||
|
||||
## What's in partially adjudicated claims data?
|
||||
|
||||
- In BCDA v3, partially adjudicated claims data:
|
||||
- Are represented with 1 FHIR resource type:
|
||||
- <a href="https://hl7.org/fhir/R4/explanationofbenefit.html" target="_blank" rel="noopener noreferrer">ExplanationOfBenefit (EOB)</a> - Information about the professional and institutional claims that providers submit for payment and information about a claim's adjudication status and processing results
|
||||
- Contain Part A and Part B data from the current performance year and historical claims data from the previous 2 or more performance years, depending on your organization’s Alternative Payment Model.
|
||||
- In BCDA v2, partially adjudicated claims data:
|
||||
- Are represented with 2 FHIR resource types:
|
||||
- <a href="https://hl7.org/fhir/R4/claim.html" target="_blank" rel="noopener noreferrer">Claim</a> - Information about the professional and institutional claims that providers submit for payment (including the services that enrollees receive)
|
||||
- <a href="https://hl7.org/fhir/R4/claimresponse.html" target="_blank" rel="noopener noreferrer">ClaimResponse</a> - Information about a claim's adjudication status and processing results
|
||||
- Contain Part A and Part B data (excluding Durable Medical Equipment) with updates from the past 60 days
|
||||
|
||||
## What's in fully adjudicated claims data?
|
||||
|
||||
In all versions of BCDA, adjudicated claims data:
|
||||
- Are represented with 3 FHIR resource types:
|
||||
- <a href="https://hl7.org/fhir/R4/patient.html" target="_blank" rel="noopener noreferrer">Patient</a> - Enrollees' demographic details and updates to their patient identifiers.
|
||||
- <a href="https://hl7.org/fhir/R4/coverage.html" target="_blank" rel="noopener noreferrer"> Coverage</a> - Enrollees' insurance coverage details, including dual coverage.
|
||||
- <a href="https://hl7.org/fhir/R4/explanationofbenefit.html" target="_blank" rel="noopener noreferrer"> ExplanationOfBenefit</a> - Details for episodes of care, including where and when the service was performed, diagnosis codes, provider, and cost of care.
|
||||
- Contain Part A, Part B, and Part D data from the current performance year and historical claims data from the previous 2 or more performance years, depending on your organization's Alternative Payment Model
|
||||
|
||||
|
||||
## How do I know when a claim has been fully processed?
|
||||
|
||||
|
||||
All EOBs contain the <a href="https://hl7.org/fhir/R4/explanationofbenefit-definitions.html#ExplanationOfBenefit.outcome" target="_blank" rel="noopener noreferrer">ExplanationOfBenefit.outcome</a> element which will indicate if the claim is still processing. If you wish to include only EOBs that have been fully processed and paid in your job requests, see our guidance on <a href="{{ '/api-documentation/filter-claims-data.html#the-_typefilter-parameter' | relative_url }}">filtering claims by `ExplanationOfBenefit.outcome` using _typeFilter</a>.
|
||||
|
||||
### Matching multiple EOBs to the same claim
|
||||
|
||||
In v3, claims are automatically deduplicated within a source system. You won't need to deduplicate multiple versions of a claim within the same System-Type.
|
||||
|
||||
However, if you are receiving claims with the `SharedSystem` System-Type AND claims with the `NationalClaimsHistory` System-Type, then it is possible to receive two versions of the same claim as two different ExplanationOfBenefit resources.
|
||||
|
||||
To identify EOBs that represent the same claim, use the `CLM-CNTL-NUM` identifier `ExplanationOfBenefit.identifier.where('system'='https://bluebutton.cms.gov/fhir/CodeSystem/CLM-CNTL-NUM').value` to identify the matching pair. If two EOBs have the same Claim Control Number, then they represent the same claim.
|
||||
|
||||
Example identifier element with claim control number:
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
"identifier": [
|
||||
{
|
||||
"system": "https://bluebutton.cms.gov/identifiers/CLM-CNTL-NUM",
|
||||
"value": "-01614121123648SZF"
|
||||
}
|
||||
]
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" can_copy=true %}
|
||||
|
||||
There may be some instances in which a claim can't be de-duplicated via `CLM-CNTL-NUM`:
|
||||
|
||||
- #### Changes in Claim Control Number
|
||||
If a claim receives a new claim control number, a previous claim control number will be available in `ExplanationOfBenefit.related`, and can be used to de-duplicate claims even if the claim control number has changed.
|
||||
|
||||
- #### PDE Claims
|
||||
Because Medicare does not process Part D / Prescription claims directly, the rules to identify related claims are a bit different than Part A / Part B claims.
|
||||
|
||||
Part D claims are submitted to CMS by the Part D plan sponsors. As a result, not all Part D claims have a claim control number. However, those that do have a claim control number can be deduplicated using it.
|
||||
|
||||
For claims without a claim control number, you can use a natural key to link together related Part D Events for a given patient. The natural key logic is outlined in <a href="https://www.cms.gov/files/document/cclf-information-packet.pdf" target="_blank" rel="noopener">CCLF Information Packet</a> under Section 5.1.2, and repeated here:
|
||||
|
||||
- Line Item From Date (`CLM_LINE_FROM_DT`)
|
||||
- Provider Service Identifier Qualifier Code (`PRVDR_SRVC_ID_QLFYR_CD`)
|
||||
- Service Provider Generic ID Number (`CLM_SRVC_PRVDR_GNRC_ID_NUM`)
|
||||
- RX Number (`CLM_LINE_RX_SRVC_RFRNC_NUM`)
|
||||
- RX Fill Number (`CLM_LINE_RX_FILL_NUM`)
|
||||
|
||||
- #### DME Claims
|
||||
When a claim from VMS is processed and released to the NCH, the leading digit (a 1) is dropped from the `CLM-CNTL-NUM`, due to a design decision made in preparation for Y2K.
|
||||
<table class="usa-table usa-table--borderless margin-bottom-4">
|
||||
<caption>Example</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">VMS Claim Control Number</th>
|
||||
<th scope="col">NCH Claim Control Number</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1222222222222222</td>
|
||||
<td>222222222222222</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Tracking a Claim After it is Fully Processed
|
||||
|
||||
After a claim is fully adjudicated and released to the National Claims History system, it is still possible for updates to be made to the claim. Similar to matching ExplanationOfBenefit resources from different sources in the same export, you may need logic to match a claim from a current export to an existing claim from a previous export. Most of the time, you can still rely on the claim control number or the parent/original claim control number in `ExplanationOfBenefit.related`.
|
||||
|
||||
A couple of quirks to call out:
|
||||
|
||||
- **Professional claims:** NCH professional claims will never populate a new claim control number. Each new version of the claim will appear to have the original claim control number.
|
||||
- **Institutional claims:** If an NCH institutional claim receives multiple rounds of claim control numbers, only the parent (the immediately-preceding claim control number) will be populated in `ExplanationOfBenefit.related`.
|
||||
|
||||
## Get started with partially adjudicated claims data
|
||||
|
||||
Access partially adjudicated claims data in the <a href="{{ '/api-documentation/get-a-bearer-token.html#sample-claims-data-sets' | relative_url }}">BCDA Sandbox</a>.
|
||||
|
||||
Review the <a href="{{ '/assets/downloads/BCDA_v3_Data_Dictionary.xlsx' | relative_url }}" data-tealium="download">Data Dictionary {% include sprite.html icon="file_download" class="text-middle"%}</a> for a complete list and description of the data fields.
|
||||
|
||||
## Use cases
|
||||
|
||||
### Example 1: Improve transition of care
|
||||
|
||||
<p class="usa-intro font-ui-md text-italic text-base">REACH ACOs can check if post-discharge processes are in place for
|
||||
an attributed enrollee shortly after a hospital discharge. Benefits include reduced readmissions and healthcare costs.
|
||||
</p>
|
||||
|
||||
On May 1, 2022, Mrs. Gonzales is doing better after a bout of pneumonia and is released home from the hospital. The hospital submits a claim for her stay to Medicare on May 3, 2022.
|
||||
|
||||
1. BCDA receives partially adjudicated claims data that reflects this update. This is identified by a Document Control Number (DCN) within a few days.
|
||||
2. A REACH ACO sets up a dashboard to track newly identified discharges. After receiving an automated care-coordination alert for Mrs. Gonzales, they're able to follow up on her post-discharge care in a timely manner.
|
||||
3. Using supplemental data, the REACH ACO adds filters to the dashboard for parameters of interest like whether the discharging hospital is in-network.
|
||||
4. BCDA will reflect any status changes to the claim, for example if Mrs. Gonzales' claim is returned to the provider or adjusted.
|
||||
5. These changes may involve updates to key variables used for care coordination, like diagnosis codes. The claims data will indicate the final status once a claim finishes adjudication and is submitted for payment.
|
||||
6. BCDA's adjudicated data will pick up this claim after several additional processing steps. This is typically up to 14 days after adjudication.
|
||||
|
||||
REACH ACOs can also flag and identify patients who have a high risk of readmission based on factors like past diagnosis. Early access to claims data can help reduce readmissions and healthcare costs.
|
||||
|
||||
### Example 2: Identify opportunities for intervention
|
||||
|
||||
<p class="usa-intro font-ui-md text-italic text-base">REACH ACOs can monitor outpatient events for follow-up care and
|
||||
track patterns that often indicate future utilization. The data can be used to target case management and deliver
|
||||
clinically appropriate follow-up care. Additionally it can help reduce the volume of unnecessary procedures.</p>
|
||||
|
||||
On September 6, 2022, Mr. Fritz underwent a duplex scan to evaluate for carotid artery stenosis. The cardiology clinic submits a claim for this procedure to Medicare on September 8, 2022.
|
||||
|
||||
1. BCDA receives partially adjudicated claims data that reflects this update. This is identified by a Claim Control Number in a few days.
|
||||
2. A REACH ACO creates a dashboard to track procedures that merit review. Mr. Fritz's service is flagged because it signals the surgeon may be planning a carotid endarterectomy procedure.
|
||||
3. However, if Mr. Fritz does not have neurological symptoms, this procedure may not be a recommended course of action. The REACH ACO is able to activate protocols to alert Mr. Fritz's primary care provider for intervention.
|
||||
4. As with example 1, BCDA will reflect any status changes to the claim.
|
||||
5. Similarly, BCDA's adjudicated data will pick up this claim.
|
||||
|
||||
REACH ACOs can also track performance on cases where the care may be of limited value. For example, if Mr. Fritz does not have neurologic symptoms, then this kind of scan has been indicated to be more harmful than beneficial. This information could be used by the REACH ACO to reduce the volume of unnecessary procedures.
|
||||
|
||||
### Example 3: Enhance care coordination
|
||||
|
||||
<p class="usa-intro font-ui-md text-italic text-base">REACH ACOs can learn more about their patients' health histories, build their clinical profiles, and improve care coordination.</p>
|
||||
|
||||
Ms. Thompson began treatment for breast cancer on March 8, 2023. She is experiencing nausea and dehydration as a result of chemotherapy. While visiting family on March 14, she drove to an emergency room (ER) 50 miles from home. She was treated with IV fluids and nausea medication.
|
||||
|
||||
1. BCDA receives partially adjudicated claims data that reflects this update. This is identified by a Claim Control Number in a few days.
|
||||
2. This emergency room visit may provide useful insights into Ms. Thompson's health history and chemotherapy journey. If Ms. Thompson is experiencing symptoms, her plan of treatment may need to be adjusted moving forward.
|
||||
3. REACH ACOs can track procedures and services, including those from a different system, to get a holistic view of patients' health histories. They can then follow up with the patient and/or provider to improve care coordination.
|
||||
|
||||
REACH ACOs can also monitor the rate of emergency services used by patients undergoing active cancer treatment and following transplantation.
|
||||
|
||||
In the past, it would take weeks to know whether patients received care outside the network. Partially adjudicated claims data allows REACH ACOs to more quickly identify and investigate out-of-network care.
|
||||
|
||||
## Additional resources
|
||||
|
||||
- <a href="https://www.medicare.gov/what-medicare-covers" target="_blank" rel="noopener noreferrer">Services covered by Medicare Parts A and B</a>
|
||||
- <a href="https://4innovation.cms.gov/secure/knowledge-management/view/341" target="_blank" rel="noopener noreferrer">Original Medicare (OM) Claims Processing</a> (requires 4i access)
|
||||
- <a href="https://4innovation.cms.gov/secure/knowledge-management/view/491" target="_blank" rel="noopener noreferrer">Reporting and Data Sharing Overview</a> (requires 4i access)
|
||||
156
dev/vendor/bcda-static-site/src/_pages/support/faqs.md
vendored
Normal file
156
dev/vendor/bcda-static-site/src/_pages/support/faqs.md
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
layout: support
|
||||
page_title: "Frequently Asked Questions"
|
||||
seo_title: ""
|
||||
description: "Frequently asked questions about BCDA."
|
||||
in-page-nav: true
|
||||
---
|
||||
|
||||
# {{ page.page_title }}
|
||||
|
||||
## About the API
|
||||
|
||||
### Who is eligible to use Beneficiary Claims Data API (BCDA)?
|
||||
|
||||
BCDA supports organizations (entities) participating in the following CMS [alternative payment models](https://www.cms.gov/priorities/innovation/about/alternative-payment-models){:target="_blank" rel="noopener"}:
|
||||
|
||||
- [ACCESS (Advancing Chronic Care with Effective, Scalable Solutions)](https://www.cms.gov/priorities/innovation/innovation-models/access){:target="_blank" rel="noopener"}
|
||||
- [ACO REACH (Accountable Care Organization Realizing Equity, Access, and Community Health)](https://www.cms.gov/priorities/innovation/innovation-models/aco-reach){:target="_blank" rel="noopener"}
|
||||
- [GUIDE (Guiding an Improved Dementia Experience)](https://www.cms.gov/priorities/innovation/innovation-models/guide){:target="_blank" rel="noopener"}
|
||||
- [IOTA (Increasing Organ Transplant Access)](https://www.cms.gov/priorities/innovation/innovation-models/iota){:target="_blank" rel="noopener"}
|
||||
- [KCC (Kidney Care Choices)](https://www.cms.gov/priorities/innovation/innovation-models/kidney-care-choices-kcc-model){:target="_blank" rel="noopener"}
|
||||
- [SSP (Shared Savings Program)](https://www.cms.gov/medicare/payment/fee-for-service-providers/shared-savings-program-ssp-acos){:target="_blank" rel="noopener"}
|
||||
|
||||
### When do terminated and discontinued model entities lose API access?
|
||||
|
||||
Terminated and discontinued organizations lose access to the API, including runouts data, the same day their participation in the model ends.
|
||||
|
||||
### What's the difference between BCDA and CCLF files?
|
||||
|
||||
CCLF files are automatically available monthly using 12 flat files, and can be downloaded weekly upon request.
|
||||
|
||||
BCDA updates adjudicated claims weekly and partially adjudicated claims 4x/week using 3 NDJSON files (Coverage, Patient, ExplanationOfBenefit).
|
||||
|
||||
Additionally, BCDA is an API that uses the [Bulk Fast Healthcare Interoperability Resources (FHIR)](https://hl7.org/fhir/uv/bulkdata/){:target="_blank" rel="noopener noreferrer"} format, as required by CMS. [Learn more about the differences]({{ '/bcda-data/comparison-bcda-cclf-files.html' | relative_url }}).
|
||||
|
||||
### What's the difference between BCDA v2 and v3?
|
||||
|
||||
BCDA v3 introduces a number of improvements, including:
|
||||
|
||||
- More frequent and timely updates
|
||||
- Easier claims tracking
|
||||
- Enhanced filtering capabilities
|
||||
- Simplified, reliable data mapping capabilities
|
||||
- Improved conformance with select FHIR Implementation Guides
|
||||
- Simplified linking between partially and fully adjudicated claims
|
||||
- Uses consistent claim identifiers across all phases of adjudication
|
||||
|
||||
We’ve also corrected issues some users encountered with v1 and v2 such as:
|
||||
|
||||
- Mismatched data between BCDA resources and CCLF files
|
||||
- Missing data for newly attributed enrollees
|
||||
- Issues for enrollees assigned more than one BENE_ID
|
||||
|
||||
You can learn more about v3 improvements and problems solved at [Introducing BCDA v3]({{ '/about/introducing-v3.html' | relative_url }}).
|
||||
|
||||
## Compliance and restrictions
|
||||
{: .margin-bottom-2}
|
||||
|
||||
### How do I redact PHI and PII when sharing information?
|
||||
|
||||
Completely cover or label [Personally Identifiable Information (PII)](https://www.hhs.gov/answers/hhs-administrative/what-is-pii/index.html) and [Protected Health Information (PHI)](https://www.hhs.gov/answers/hipaa/what-is-phi/index.html) as “REDACTED” in emails, screenshots, and documents. Ensure any masks are 100% opaque and in a format that supports layers.
|
||||
|
||||
Examples of PII and PHI:
|
||||
|
||||
- Medicare Beneficiary Identifier (MBI)
|
||||
- Taxpayer Identification Number (TIN)
|
||||
- National Provider Identifier (NPI)
|
||||
- Social Security Number (SSN)
|
||||
- API keys or access credentials (e.g., client ID and secret)
|
||||
- authorization or bearer tokens
|
||||
|
||||
PII and PHI are often found in API requests or response payloads referenced in:
|
||||
|
||||
- the text of your Google Group posts or emails
|
||||
- files attached to your Google Group posts or emails (e.g., XMLs, JSONs)
|
||||
- screenshots attached to your Google Group posts or emails
|
||||
|
||||
Example of a redacted response:
|
||||
|
||||
{% capture curlSnippet %}{% raw %}
|
||||
{
|
||||
"taxpayerIdentificationNumber": "REDACTED",
|
||||
"nationalProviderIdentifier": "REDACTED"
|
||||
}
|
||||
{% endraw %}{% endcapture %}
|
||||
{% include copy_snippet.html code=curlSnippet language="json" %}
|
||||
|
||||
### Can I use 3rd party web-based REST clients or tools?
|
||||
|
||||
Use caution with 3rd party web-based clients. By sharing your credentials, you may be allowing them to make REST calls on your behalf. This is a serious data privacy and security risk. We recommend using secure REST client tools.
|
||||
|
||||
## Claims processing timeline
|
||||
|
||||
### How long does it take BCDA to receive a claim after it is submitted?
|
||||
|
||||
It typically takes 2-4 days after submission for BCDA to receive [partially adjudicated claims data]({{ '/bcda-data/partially-adjudicated-claims-data.html' | relative_url }}) and up to 7 days for adjudicated claims data. Even after adjudication, claims may go through additional processing. BCDA will continue to provide the latest updates available for each claim.
|
||||
|
||||
According to Section 6404 of the Affordable Care Act, Original Medicare claims must be submitted within 12 months (1 calendar year) of the date of service.
|
||||
|
||||
### Where does the data come from?
|
||||
|
||||
BCDA v3 sources data from CMS's Integrated Data Repository (IDR).
|
||||
|
||||
For earlier versions of BCDA, adjudicated claims data is loaded from the [Chronic Conditions Data Warehouse (CCW)](https://www2.ccwdata.org/web/guest/home/){:target="_blank" rel="noopener noreferrer"}. Partially adjudicated claims data is loaded from the Fiscal Intermediary Standard System (FISS) and Multi-Carrier System (MCS).
|
||||
|
||||
### How often is data refreshed?
|
||||
|
||||
The following table shows how frequently Medicare claims data is refreshed and potentially available to eligible organizations.
|
||||
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked margin-bottom-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">FHIR Resource</th>
|
||||
<th scope="col">BCDA v3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Patient</th>
|
||||
<td>6x/week, Sunday to Friday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Coverage</th>
|
||||
<td>6x/week, Sunday to Friday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">ExplanationOfBenefit - Part A/B (National Claims History)</th>
|
||||
<td>Weekly, on Monday<sup><a href="#refresh-fn1">1</a></sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">ExplanationOfBenefit - Part D</th>
|
||||
<td>5x/week, Sunday to Thursday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">ExplanationOfBenefit - Part A/B (Shared Systems)</th>
|
||||
<td>4x/week, Sunday and Tuesday to Thursday</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<sup>1</sup> Data is loaded on the Monday after it's loaded into the source system, 0-7 days from processing or "adjudication."
|
||||
{: #refresh-fn1 .font-body-2xs style="scroll-margin-top: 6.25rem;"}
|
||||
|
||||
To stay up to date with the latest claims data for your enrollees, we recommend exporting data from National Claims History once per week, and daily for all other sources. Use the [_since parameter]({{ '/api-documentation/filter-claims-data.html#the-_since-parameter' | relative_url }}) when running jobs to avoid downloading duplicate data.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Why do I get connection timeout issues with my production credentials?
|
||||
|
||||
BCDA requires that all production requests come from a registered IP address. Make sure the IP addresses you're using to request data have been added to the Allow List in your model-specific system. Visit [Production Access]({{ '/production-access.html' | relative_url }}) for more details.
|
||||
|
||||
### Why am I getting a 429 error response?
|
||||
|
||||
A status code of 429 indicates “Too Many Requests.” Wait until the period of time specified in the header has passed before making more requests.
|
||||
|
||||
This makes sure your client can adapt without manual intervention, even if the rate-limiting parameters change. [Learn more about the 429 status code.]({{ '/api-documentation/access-claims-data.html' | relative_url }}#response-example-too-many-requests)
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack api serve
|
||||
sidebar_position: 79
|
||||
sidebar_position: 80
|
||||
---
|
||||
|
||||
# `stack api serve`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack api
|
||||
sidebar_position: 78
|
||||
sidebar_position: 79
|
||||
---
|
||||
|
||||
# `stack api`
|
||||
|
||||
20
docs/docs/cli/bcda-errors.md
Normal file
20
docs/docs/cli/bcda-errors.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: stack bcda errors
|
||||
sidebar_position: 19
|
||||
---
|
||||
|
||||
# `stack bcda errors`
|
||||
|
||||
```
|
||||
Usage: stack bcda errors [OPTIONS] KEY
|
||||
|
||||
OperationOutcome issues from a job's error files (a partial export).
|
||||
|
||||
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
|
||||
│ * key TEXT Store job key. [required] │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
╭─ Options ────────────────────────────────────────────────────────────────────╮
|
||||
│ --limit INTEGER Issues to print. [default: 50] │
|
||||
│ --help Show this message and exit. │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
@@ -28,9 +28,13 @@ Usage: stack bcda export [OPTIONS]
|
||||
│ --system-type TEXT _typeFilter System-Type │
|
||||
│ tag(s). │
|
||||
│ --outcome TEXT _typeFilter outcome: partial │
|
||||
│ or complete. │
|
||||
│ --service-date-from TEXT _typeFilter service-date > │
|
||||
│ YYYY-MM-DD. │
|
||||
│ and/or complete. │
|
||||
│ --service-date-from TEXT _typeFilter earliest │
|
||||
│ service-date (YYYY[-MM[-DD]], │
|
||||
│ optional gt/ge/eq prefix). │
|
||||
│ --service-date-to TEXT _typeFilter latest │
|
||||
│ service-date (optional lt/le │
|
||||
│ prefix). │
|
||||
│ --per-type --one-job One job per type (survives │
|
||||
│ sandbox sweeps). │
|
||||
│ [default: per-type] │
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bcda load
|
||||
sidebar_position: 19
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
# `stack bcda load`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bcda sync
|
||||
sidebar_position: 21
|
||||
sidebar_position: 22
|
||||
---
|
||||
|
||||
# `stack bcda sync`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bcda vendor
|
||||
sidebar_position: 20
|
||||
sidebar_position: 21
|
||||
---
|
||||
|
||||
# `stack bcda vendor`
|
||||
|
||||
@@ -25,6 +25,8 @@ Usage: stack bcda [OPTIONS] COMMAND [ARGS]...
|
||||
│ manifest. │
|
||||
│ cancel Cancel an in-progress job. │
|
||||
│ store Local store: jobs, files, bytes. │
|
||||
│ errors OperationOutcome issues from a job's error files (a partial │
|
||||
│ export). │
|
||||
│ load Reconcile the store (or flatten the newest export per type), │
|
||||
│ load, run the connector, publish the replica. │
|
||||
│ vendor Vendored upstream (CMSgov/bcda-app, bcda.cms.gov): manifest, │
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib attach-iom
|
||||
sidebar_position: 34
|
||||
sidebar_position: 35
|
||||
---
|
||||
|
||||
# `stack bib attach-iom`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib attach-oig
|
||||
sidebar_position: 39
|
||||
sidebar_position: 40
|
||||
---
|
||||
|
||||
# `stack bib attach-oig`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib backfill-comments
|
||||
sidebar_position: 32
|
||||
sidebar_position: 33
|
||||
---
|
||||
|
||||
# `stack bib backfill-comments`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib cfr-jump
|
||||
sidebar_position: 44
|
||||
sidebar_position: 45
|
||||
---
|
||||
|
||||
# `stack bib cfr-jump`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib code-tags
|
||||
sidebar_position: 45
|
||||
sidebar_position: 46
|
||||
---
|
||||
|
||||
# `stack bib code-tags`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib discover-pfs-rules
|
||||
sidebar_position: 28
|
||||
sidebar_position: 29
|
||||
---
|
||||
|
||||
# `stack bib discover-pfs-rules`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib fetch-docket-comments
|
||||
sidebar_position: 29
|
||||
sidebar_position: 30
|
||||
---
|
||||
|
||||
# `stack bib fetch-docket-comments`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib fetch-pfs-comments
|
||||
sidebar_position: 30
|
||||
sidebar_position: 31
|
||||
---
|
||||
|
||||
# `stack bib fetch-pfs-comments`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib fr-grab
|
||||
sidebar_position: 42
|
||||
sidebar_position: 43
|
||||
---
|
||||
|
||||
# `stack bib fr-grab`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib fr-jump
|
||||
sidebar_position: 43
|
||||
sidebar_position: 44
|
||||
---
|
||||
|
||||
# `stack bib fr-jump`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib import-zotero
|
||||
sidebar_position: 36
|
||||
sidebar_position: 37
|
||||
---
|
||||
|
||||
# `stack bib import-zotero`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib ingest-iom
|
||||
sidebar_position: 33
|
||||
sidebar_position: 34
|
||||
---
|
||||
|
||||
# `stack bib ingest-iom`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib ingest-mail
|
||||
sidebar_position: 31
|
||||
sidebar_position: 32
|
||||
---
|
||||
|
||||
# `stack bib ingest-mail`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib ingest-oig
|
||||
sidebar_position: 38
|
||||
sidebar_position: 39
|
||||
---
|
||||
|
||||
# `stack bib ingest-oig`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib query
|
||||
sidebar_position: 27
|
||||
sidebar_position: 28
|
||||
---
|
||||
|
||||
# `stack bib query`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib refresh-iom
|
||||
sidebar_position: 41
|
||||
sidebar_position: 42
|
||||
---
|
||||
|
||||
# `stack bib refresh-iom`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib refresh-oig
|
||||
sidebar_position: 40
|
||||
sidebar_position: 41
|
||||
---
|
||||
|
||||
# `stack bib refresh-oig`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib sync-zotero
|
||||
sidebar_position: 37
|
||||
sidebar_position: 38
|
||||
---
|
||||
|
||||
# `stack bib sync-zotero`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib sync
|
||||
sidebar_position: 25
|
||||
sidebar_position: 26
|
||||
---
|
||||
|
||||
# `stack bib sync`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib tag
|
||||
sidebar_position: 26
|
||||
sidebar_position: 27
|
||||
---
|
||||
|
||||
# `stack bib tag`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib watch-iom
|
||||
sidebar_position: 35
|
||||
sidebar_position: 36
|
||||
---
|
||||
|
||||
# `stack bib watch-iom`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bib
|
||||
sidebar_position: 24
|
||||
sidebar_position: 25
|
||||
---
|
||||
|
||||
# `stack bib`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bls ces
|
||||
sidebar_position: 48
|
||||
sidebar_position: 49
|
||||
---
|
||||
|
||||
# `stack bls ces`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bls oews
|
||||
sidebar_position: 47
|
||||
sidebar_position: 48
|
||||
---
|
||||
|
||||
# `stack bls oews`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack bls
|
||||
sidebar_position: 46
|
||||
sidebar_position: 47
|
||||
---
|
||||
|
||||
# `stack bls`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments dockets
|
||||
sidebar_position: 53
|
||||
sidebar_position: 54
|
||||
---
|
||||
|
||||
# `stack comments dockets`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments extract-ocr
|
||||
sidebar_position: 51
|
||||
sidebar_position: 52
|
||||
---
|
||||
|
||||
# `stack comments extract-ocr`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments extract
|
||||
sidebar_position: 50
|
||||
sidebar_position: 51
|
||||
---
|
||||
|
||||
# `stack comments extract`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments seal
|
||||
sidebar_position: 54
|
||||
sidebar_position: 55
|
||||
---
|
||||
|
||||
# `stack comments seal`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments stats
|
||||
sidebar_position: 52
|
||||
sidebar_position: 53
|
||||
---
|
||||
|
||||
# `stack comments stats`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments unseal
|
||||
sidebar_position: 55
|
||||
sidebar_position: 56
|
||||
---
|
||||
|
||||
# `stack comments unseal`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack comments
|
||||
sidebar_position: 49
|
||||
sidebar_position: 50
|
||||
---
|
||||
|
||||
# `stack comments`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack db comment
|
||||
sidebar_position: 72
|
||||
sidebar_position: 73
|
||||
---
|
||||
|
||||
# `stack db comment`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack db inspect
|
||||
sidebar_position: 73
|
||||
sidebar_position: 74
|
||||
---
|
||||
|
||||
# `stack db inspect`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack db
|
||||
sidebar_position: 71
|
||||
sidebar_position: 72
|
||||
---
|
||||
|
||||
# `stack db`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack docs build
|
||||
sidebar_position: 75
|
||||
sidebar_position: 76
|
||||
---
|
||||
|
||||
# `stack docs build`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack docs generate
|
||||
sidebar_position: 77
|
||||
sidebar_position: 78
|
||||
---
|
||||
|
||||
# `stack docs generate`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack docs serve
|
||||
sidebar_position: 76
|
||||
sidebar_position: 77
|
||||
---
|
||||
|
||||
# `stack docs serve`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack docs
|
||||
sidebar_position: 74
|
||||
sidebar_position: 75
|
||||
---
|
||||
|
||||
# `stack docs`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack generate models
|
||||
sidebar_position: 23
|
||||
sidebar_position: 24
|
||||
---
|
||||
|
||||
# `stack generate models`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack generate
|
||||
sidebar_position: 22
|
||||
sidebar_position: 23
|
||||
---
|
||||
|
||||
# `stack generate`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack lake deploy
|
||||
sidebar_position: 57
|
||||
sidebar_position: 58
|
||||
---
|
||||
|
||||
# `stack lake deploy`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack lake load
|
||||
sidebar_position: 58
|
||||
sidebar_position: 59
|
||||
---
|
||||
|
||||
# `stack lake load`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack lake parity
|
||||
sidebar_position: 60
|
||||
sidebar_position: 61
|
||||
---
|
||||
|
||||
# `stack lake parity`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack lake validate
|
||||
sidebar_position: 59
|
||||
sidebar_position: 60
|
||||
---
|
||||
|
||||
# `stack lake validate`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack lake
|
||||
sidebar_position: 56
|
||||
sidebar_position: 57
|
||||
---
|
||||
|
||||
# `stack lake`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm eval-tags
|
||||
sidebar_position: 68
|
||||
sidebar_position: 69
|
||||
---
|
||||
|
||||
# `stack llm eval-tags`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm hosts
|
||||
sidebar_position: 64
|
||||
sidebar_position: 65
|
||||
---
|
||||
|
||||
# `stack llm hosts`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm index
|
||||
sidebar_position: 62
|
||||
sidebar_position: 63
|
||||
---
|
||||
|
||||
# `stack llm index`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm prune-rules
|
||||
sidebar_position: 69
|
||||
sidebar_position: 70
|
||||
---
|
||||
|
||||
# `stack llm prune-rules`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm restamp
|
||||
sidebar_position: 63
|
||||
sidebar_position: 64
|
||||
---
|
||||
|
||||
# `stack llm restamp`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm serve
|
||||
sidebar_position: 65
|
||||
sidebar_position: 66
|
||||
---
|
||||
|
||||
# `stack llm serve`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm stamp-themes
|
||||
sidebar_position: 70
|
||||
sidebar_position: 71
|
||||
---
|
||||
|
||||
# `stack llm stamp-themes`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm tag
|
||||
sidebar_position: 67
|
||||
sidebar_position: 68
|
||||
---
|
||||
|
||||
# `stack llm tag`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm vocab
|
||||
sidebar_position: 66
|
||||
sidebar_position: 67
|
||||
---
|
||||
|
||||
# `stack llm vocab`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack llm
|
||||
sidebar_position: 61
|
||||
sidebar_position: 62
|
||||
---
|
||||
|
||||
# `stack llm`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail attach-smarthost
|
||||
sidebar_position: 120
|
||||
sidebar_position: 121
|
||||
---
|
||||
|
||||
# `stack mail attach-smarthost`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail dkim-export
|
||||
sidebar_position: 119
|
||||
sidebar_position: 120
|
||||
---
|
||||
|
||||
# `stack mail dkim-export`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail dns
|
||||
sidebar_position: 118
|
||||
sidebar_position: 119
|
||||
---
|
||||
|
||||
# `stack mail dns`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail down
|
||||
sidebar_position: 116
|
||||
sidebar_position: 117
|
||||
---
|
||||
|
||||
# `stack mail down`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail provision
|
||||
sidebar_position: 114
|
||||
sidebar_position: 115
|
||||
---
|
||||
|
||||
# `stack mail provision`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail rotate-creds
|
||||
sidebar_position: 121
|
||||
sidebar_position: 122
|
||||
---
|
||||
|
||||
# `stack mail rotate-creds`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail seed-mailboxes
|
||||
sidebar_position: 122
|
||||
sidebar_position: 123
|
||||
---
|
||||
|
||||
# `stack mail seed-mailboxes`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail status
|
||||
sidebar_position: 117
|
||||
sidebar_position: 118
|
||||
---
|
||||
|
||||
# `stack mail status`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail up
|
||||
sidebar_position: 115
|
||||
sidebar_position: 116
|
||||
---
|
||||
|
||||
# `stack mail up`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail wire-git
|
||||
sidebar_position: 123
|
||||
sidebar_position: 124
|
||||
---
|
||||
|
||||
# `stack mail wire-git`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack mail
|
||||
sidebar_position: 113
|
||||
sidebar_position: 114
|
||||
---
|
||||
|
||||
# `stack mail`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack perf show
|
||||
sidebar_position: 81
|
||||
sidebar_position: 82
|
||||
---
|
||||
|
||||
# `stack perf show`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack perf
|
||||
sidebar_position: 80
|
||||
sidebar_position: 81
|
||||
---
|
||||
|
||||
# `stack perf`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: stack pfs cpt-ingest
|
||||
sidebar_position: 95
|
||||
sidebar_position: 96
|
||||
---
|
||||
|
||||
# `stack pfs cpt-ingest`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user