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
175 lines
5.1 KiB
Go
175 lines
5.1 KiB
Go
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
|
|
}
|