Method paper

How every number is calculated

Every score, rate, projection and settlement figure mdvin puts on a screen — the question it answers, the logic behind the way it is built, the formula, a worked example in real numbers, and how to check it without taking our word for anything.

mdvin · prior authorization and value-based care orchestration · BvLogic Solutions LLC

What this paper is

A platform that ranks people for clinical outreach and estimates money owed under a contract is making claims about the world. Those claims are worth exactly what the arithmetic behind them is worth, so this document states all of it: not a summary of the approach, but the formula behind each output with the function that computes it named beside it.

Every calculation here is held to three commitments, and they are the reason the engines look the way they do rather than the way a modelling exercise would look:

The worked examples are not illustrations. Each is the engine's own output on the demonstration population, printed by Docs/evidence/2026-09/2026-09-24_worked_examples.py and quoted here unchanged.

1 · The method

Four decisions that shape every engine in this paper

These are not style preferences. Each was taken for a reason specific to running clinical outreach under a regulated contract, and each costs something — which is why they are stated rather than assumed.

Rules, not a learned model

The obvious instinct for risk stratification is gradient boosting. It is the wrong first move here, for three reasons. The output format already demands reasons — the member panel renders each risk factor with its provenance, which a rule engine produces natively and a tree ensemble only ever reconstructs afterwards. A risk tier drives outreach, and outreach is regulated — a score that affects what care somebody is offered must be explainable, disclosed and defensible in discovery. And there is no training data: a synthetic cohort is not a training set, and fitting to it would produce a model whose version string meant nothing. When real longitudinal data exists these rules become the baseline a learned model has to beat, and the factor format they already emit is what makes that comparison possible.

Computed at read time, never stored

No score in this paper has a column in the database. A persisted priority is stale the moment a clock moves, and it would need its own recompute job that can drift from what the screen shows. The cost is that every page load does the arithmetic; the benefit is that the number on screen is always the number the current facts produce. Risk scores are the deliberate exception: each run is appended as history, so what a score meant last quarter is still recoverable.

Clinical logic is code; policy is data

Measure specifications, targets, contract terms and SLA lengths are versioned records rather than constants in a source file, so changing one is a data change with an audit row behind it and a measure's numerator can be restated without a release. What stays in code is the logic that must not vary by tenant: what it means for a gap to be open, who may write to a member's record, when an authorization blocks scheduling.

Nothing here decides

Every recommendation names the endpoint a human would call and calls none of them. The server applies its own guards regardless of what the screen suggested — scheduling stays blocked while a linked authorization is pending, and the refusal is visible rather than pre-empted. The platform connects the workflow; the clinician provides the judgment.

2 · Population health

Clinical risk score and tier

app/services/risk.py · score_patient() · model apex-risk-rules v2
The question

Of the people this plan is responsible for, who is most likely to have an expensive, avoidable year — and why, in terms a clinician can argue with?

The logic

Risk is additive across independent signals, each drawn from a different source system, because that is how a clinician reasons about it: a diagnosis list, recent labs, social circumstances, what the hospital sent over, whether the member is actually taking the medication. Each rule that fires contributes its points and records what it saw and where it came from, so the score is never more than the sum of statements you can check one at a time. Only the most severe band per lab fires, so one bad result is counted once rather than twice.

The formula
score = the sum of the points of every rule that fires
tier  = HIGH if score ≥ 60 · RISING if ≥ 35 · MEDIUM if ≥ 15 · else LOW
Active conditionPoints
Heart failure I50.914
Chronic kidney disease stage 3 N18.312
Coronary artery disease I25.1010
Diabetes with hyperglycemia E11.6510
Type 2 diabetes E11.98
Hypertension I105
Obstructive sleep apnea G47.334
Hyperlipidemia E78.54
Three or more chronic conditions+8
Measured value — latest, 730-day lookbackPoints
HbA1c above 9.0 4548-418
HbA1c above 8.010
Systolic BP above 160 8480-614
Systolic BP above 1409
Creatinine above 1.5 2160-010
Total cholesterol above 240 2093-36

Only the most severe band per lab fires: an HbA1c of 9.4 scores 18, not 28.

Social risk — Z-code answered yesPoints
Homelessness Z59.010
Food insecurity Z59.48
Transportation insecurity Z59.827
Social isolation Z60.25
Utilisation and adherence — 365-day lookbackPoints
ED visits, 6 eachcapped 18
Inpatient admissions, 10 eachcapped 20
Each active medication below 80% PDC7
Worked example — John Doe, exactly as the engine returns him
PointsFactor that firedSource
12Chronic kidney disease stage 3 (N18.3)problem_list
10Diabetes with hyperglycemia (E11.65)problem_list
10HbA1c above 8% (was 8.9%)lab
10Creatinine above 1.5 (was 1.8 mg/dL)lab
9Systolic BP above 140 (was 148.0 mmHg)ehr
83 chronic conditionsproblem_list
8Food insecurity (Z59.4)survey
7Atorvastatin 40mg adherence 62% (below 80%)pharmacy
61 ED visit in 12 monthsadt
5Hypertension (I10)problem_list
85→ tier HIGH (60 or above)10 factors, 6 source systems
How to check it. Open the member and add up the factor list on screen — it is the same list, in descending order, and it must total the score shown. Each row names the system that asserted it, so a disputed factor can be traced back to the feed it came from. If a weight ever changes, the model version changes with it and past scores keep their meaning: the history is the audit trail, the tier on the member record is only a convenience.

3 · Quality measurement

One member against one measure

app/services/quality.py · evaluate_patient()
The question

For this measure, is this member counted — and if they are counted, have they had the care it asks for?

The logic

Four buckets, mutually exclusive, so no member is ever both excluded and counted. The distinction that matters most is between not in the denominator (the measure does not apply to them) and excluded (it would apply, but a clinical exclusion releases them): conflating those two is the error that quietly inflates a rate. Denominator, numerator, exclusions and target all come from the versioned measure package rather than from code, so a specification change is a data change with a version attached.

BucketRule
Not in denominatorAge today outside the package's minimum or maximum, or the package names condition codes and the member has no active condition among them
ExcludedIn the denominator, but holds an active condition listed in the package's exclusions
CompliantIn the denominator, not excluded, and the numerator is met
Open gapIn the denominator, not excluded, and the numerator is not met
The formula — three ways a numerator can be met

Always inside the package's lookback window, 365 days by default:

  • observation — the latest qualifying result inside the window; where the package sets a maximum, compliant only if that value is at or below it (HbA1c below 8.0, systolic below 140). Latest, not best: a member who improved and then relapsed is not compliant.
  • procedure — any qualifying procedure inside the window (retinal exam, annual wellness visit). Any, not latest: having had it twice is not better than once.
  • medication_pdc — an active medication in the named class whose best proportion of days covered is at least 80%, the PQA / CMS Star Ratings adherence threshold, overridable per measure.
How to check it. Every member in a measure's denominator is listed by the drill-down behind its rate, and each row states which bucket they are in and why. The rate is computed from the same rows that list renders — so a KPI can never disagree with the list behind it, which is a requirement of the build rather than a happy property.

A measure across a population

app/services/quality.py · evaluate_tenant()
The question

How is the plan performing on this measure, and how much of that performance is still recoverable this year?

The formula
D      = members in the denominator and not excluded   (denominator)
N      = of those, the compliant                       (numerator)
X      = the excluded                                  (reported, outside D)
G      = D − N                                           (open gaps)
rate%  = round(100 × N / D, 1)      undefined, shown as a dash, when D = 0
Worked example — all five rated measures on the demonstration population
MeasureDNXOpenRateTarget
Diabetes: Eye Exam Performed60060.0%70
Diabetes: HbA1c Control (<8.0%)620433.3%75
Statin Adherence (PDC ≥ 80%)820625.0%80
Annual Wellness Visit920722.2%60
Controlling High Blood Pressure1040640.0%75

Eye exam: 100 × 0 / 6 = 0.0%. Blood pressure: 100 × 4 / 10 = 40.0%. In every row N + open = D — the numerator and the open gaps account for the whole denominator, with the excluded reported separately.

A measure is rated when D is greater than zero, and only rated measures reach the composite. That one definition is why an unrated measure never quietly scores zero — a composite of nothing is not zero, because zero is a score somebody earned.

The quality composite

app/services/quality.py · composite()
The question

One number for quality, because the contract settles on one number.

The formula
composite% = round( mean( rate% of every rated measure ), 1 )
Worked example

(0.0 + 33.3 + 25.0 + 22.2 + 40.0) / 5 = 120.5 / 5 = 24.1% — the composite the contract is judged against.

Unweighted, because that is the demonstration contract's stated scoring method. A programme that weights its measures would change every number in the opportunity table below, and the weighting would live in the contract terms rather than in code.

A property of the method, said out loud on the screen: because the mean is unweighted, measures with small denominators move the composite fastest per gap closed. Six eligible members means each closure is worth more than one in a measure with ten. That is arithmetic, not a defect — and it is precisely why the next section ranks by points per gap rather than by how many gaps are open.

4 · Value-based contract management

Quality opportunity, and what one closed gap is worth

app/services/vbc_manager.py · quality_opportunity()
The question

There are twenty-nine open gaps and a team that can work a handful this month. Which ones move the number the contract settles on?

The logic

“Opportunity” is defined here rather than left to intuition: it is the number of composite points the score would gain if every open gap in that measure closed. That is a real, checkable quantity, and it is what makes the ranking arguable — a measure twenty points below target with two eligible members matters less than one five points below target with thirty. Ranking by “most gaps” or “furthest below target” gets both of those backwards.

The formula

With n = the number of rated measures and target = the package's own target percentage:

Column on screenFormula
Performancerate%
Targetfrom the versioned measure package; “no target set” when absent
Gap to target, in pointsround(target − rate%, 1) — positive means short
“N members would close it”max( ceil(D × target / 100) − N , 0 )
Open gapsG
Composite points per gapround(100 / D / n, 2)
Composite points availableround(100 / D / n × G, 2) — from the unrounded points per gap, so this figure and the scenario engine cannot drift apart
Opportunity bandLow if at or above target · else High if available ≥ 3.0 · Medium if ≥ 1.0 · else Low
Why 100 / D / n — two separate facts, one after the other:
closing one gap raises that measure's rate by 100 / D points,
and the composite is the unweighted mean of n measures,
so the composite moves by 1/n of that.
Worked example — one rule, five different answers
MeasureDOpen100 / D÷ n = 5On screenAvailable
Diabetes: Eye Exam Performed6616.673.33333.3320.0
Diabetes: HbA1c Control6416.673.33333.3313.33
Statin Adherence8612.502.50002.515.0
Annual Wellness Visit9711.112.22222.2215.56
Controlling High Blood Pressure10610.002.00002.012.0

Five different numbers from one rule, with no exceptions. A formula that explains only the row being questioned is a rationalisation; one that explains all five is the method. Note also that the ranking is not by open gaps — the wellness visit has the most gaps (7) and is second by opportunity (15.56), behind the eye exam's six gaps (20.0).

How to check it, from a photograph of the screen. Each row prints N open and X% now, and those two give the denominator with no other source: D = open ÷ (1 − rate). Eye exam: 6 ÷ (1 − 0) = 6. Wellness visit: 7 ÷ (1 − 0.222) = 9. Then 100 ÷ D ÷ 5 reproduces the points per gap. Nothing else is needed, and nothing has to be taken on trust.

Path to the quality gate

app/services/vbc_manager.py · gate_path()
The question

The contract pays nothing on savings unless quality clears a gate. What is the cheapest route from where we are to where we must be, counted in gaps closed?

The logic

Greedy — take the measure with the highest points per gap first, then the next. Greedy is not an approximation here, it is optimal: every gap within one measure is worth exactly the same number of composite points, so taking the highest-value measure first can never cost a cheaper route later. That is a property of the unweighted mean, and it would stop being true under a weighted contract — at which point this function would have to change, and this paper would say so.

The formula
gate      = contract.minimum_quality_pct
shortfall = round(gate − composite, 1)            (0 when already met)

then, measures sorted by points_per_gap descending:
  need        = min( G , ceil( (shortfall − gained) / points_per_gap ) )
  gained     += need × points_per_gap
  rate_after% = round(100 × (N + need) / D, 1)
until gained ≥ shortfall

projected%    = round(composite + gained, 1)
achievable    = shortfall ≤ 0 or projected ≥ gate
gaps_to_close = the sum of need
Worked example — the engine's own plan

Current 24.1%, gate 60.0%, shortfall 35.9 points.

StepMeasureCloseOf openRate after
1Diabetes: Eye Exam Performed (3.33 per gap — the richest)66100.0%
2Diabetes: HbA1c Control (3.33 per gap)44100.0%
3Statin Adherence (2.5 per gap — stops as soon as the gate is cleared)2650.0%
Projected composite 62.4% — gate cleared12

Twelve gaps, not twenty-nine, and the third measure is only partly worked — the plan stops the moment the gate is cleared rather than finishing a measure for tidiness.

Scenario modelling — “what if we close more gaps?”

app/services/vbc_manager.py · scenario()
The question

If the team commits to closing a specific number of specific gaps, where does that leave the composite, the gate, and the money?

The formula
for each measure with k chosen closures, capped at G:
rate_after%      = round(100 × (N + k) / D, 1)
composite_after% = round( mean( rate_after% of every rated measure ), 1 )
gate_met_after   = composite_after ≥ gate
settlement_after = gross_savings × shared_savings_rate × (1 − quality_withhold_pct)
                   if the gate is met after and gross_savings > 0, else 0
settlement_delta = settlement_after − settlement_before
Worked example — the eye-exam measure
ClosuresMeasure rateCompositeGate met after?
Close 10.0% → 16.7%24.1% → 27.4%No
Close all 60.0% → 100.0%24.1% → 44.1%No

One closure is worth 3.33 composite points. The screen reports the composite exactly as the live score is computed — each measure's rate rounded to one decimal, then their mean rounded to one decimal — so the eye exam's 16.7% gives (16.7 + 22.2 + 25.0 + 33.3 + 40.0) ÷ 5 = 27.44, shown as 27.4, and the sentence under the result reads “Composite moves +3.3 points”: the difference of the two displayed figures. Six closures move it 20.0, matching the row's stated points available exactly. And even perfect performance on one measure does not clear a 60% gate: that is the honest answer, and it is the one the screen gives.

Assumptions the screen carries with the number: closures add to the numerator only — denominators do not move; cost performance is held constant; closures are capped at the gaps actually open. It is a projection, not a forecast.

Contract performance and shared-savings settlement

app/services/finance.py · contract_performance()
The question

Against the benchmark this contract set, are we spending less — and does any of that saving actually become money?

The logic

Two independent conditions, and the second is the one that surprises people. Savings are real only if spending came in under benchmark, and they are only payable if quality clears the gate. A programme can generate savings and receive nothing. That is why the quality composite is presented as a financial number on the contract screen rather than a clinical one, and why the gate path above exists at all.

The formula
member_months = from the cost summary for the current period
actual_pmpm   = allowed cost per member per month
variance_pmpm = round(benchmark_pmpm − actual_pmpm, 2)
gross_savings = round(variance_pmpm × member_months, 2)
gate_met      = composite ≥ contract.minimum_quality_pct
settlement    = round(gross_savings × shared_savings_rate × (1 − quality_withhold_pct), 2)
                when gross_savings > 0 and the gate is met; otherwise 0, and the note says which
Worked example — the demonstration contract
Benchmark PMPM1,750.00
Actual PMPM1,554.32
Variance per member per month195.68
Member months in the period105.12
Gross savings — 195.68 × 105.1220,569.88
Quality composite against the gate24.1% vs 60.0%
Estimated settlement — gate not met0.00

Twenty thousand dollars of real savings, and nothing payable. This is the most useful number on the contract screen: it converts a quality problem into a financial one, and it is why the platform ranks gaps by composite points rather than by clinical severity alone.

Every figure in this section is labelled an estimate on screen. Real settlement is reconciled by the payer against run-out claims months later. The platform's job is to show the trajectory and what moves it, not to promise a cheque.

5 · Prioritisation — how work gets ordered

Work-queue ranking

app/services/ranking.py · rank()
The question

A care manager opens the day with thirty-two open tasks and time for eight. Which eight?

The logic

The authored priority label is an input, not the ranking. A manual “Critical” is a snapshot of what somebody believed when they created the task; the clinical signals move afterwards. A member discharged yesterday with a contact window closing today outranks a task somebody marked Critical last month — and the earlier weighting, which put the human label at 40 points against 15 for clinical risk, could not express that. The label still counts, because discarding a clinician's judgment would be its own failure, but it can now be outscored by what is actually happening to the member.

The formula

The score is the sum of every component that fires, and each component is returned with the sentence that justifies it.

ComponentPoints
Authored priority — Critical / High / Medium / Low20 / 12 / 6 / 2
Member risk tier — HIGH / RISING / MEDIUM / LOW22 / 15 / 6 / 0
Worst open gap's priority — Critical / High / Medium / Low18 / 12 / 6 / 2
Post-discharge 48-hour contact window still open28
Contact window missed, visit window still open20
Outreach stalled — sent, nothing back14
Ageing after 3 days, one per daycapped 21
Due date and authorization clockPoints
Overdue — 35 plus 2 per day late, 10-day cap35–55
Due today25
Due within 2 days18
Due within 7 days10
Due later3
Authorization past its decision deadline30
Decision due within 24h — expedited / standard25 / 20
Decision due within 72h — expedited / standard18 / 12
On the SLA clock, beyond 72h5
Worked example — the task at the top of the queue
PointsComponentWhy
55dueoverdue by 60 days — 35 + 2 × 10, at the cap
22patient_riskhigh-risk patient
18gap_prioritycritical-priority gap: HbA1c Control
12prioritymarked High
107TotalComplete the health risk assessment

The care manager sees the composed sentence, not the number: “overdue by 60 days + high risk + HbA1c Control — Glycemic Assessment”. Note that the authored label contributed 12 of 107 — the queue is being driven by the member's situation, which is the whole point of the design.

How to check it. Every task exposes its component list. Ties break on due date, then creation time, so the order is stable across reloads rather than falling back to whatever the database returned.

My Panel — ranking members rather than tasks

app/services/panel.py · panel()
The question

Which people need me today — including the ones nobody has raised a task for?

The logic

A care manager thinks in members, not tickets. Ranking tasks splits one member with three gaps into three small jobs, so the person who needs the most attention can look like three minor items. Worse: a member who needs attention but has no task is not in the queue at all — high risk, several gaps open, nobody raised anything, invisible. This ranks the panel instead, so a member with no task can outrank a member with a stale one. The same engine scores a cohort list, so two screens cannot disagree about how much a member needs attention.

The formula
SignalPointsWhy it carries weight
Risk tier HIGH / RISING / MEDIUM / LOW30 / 20 / 8 / 0The strongest single predictor the plan holds
Each open gap nobody is working8Unworked, not merely open
Each gap already in flight2Still present, much quieter
Discharge contact window open25It closes whether or not anybody acts
Contact window already missed15The visit window may still be saved
No active coverage18As hard a block as a pending authorization, and quieter: nothing is pending, so nothing prompts anyone to look
Outreach stalled14Needs a different channel, and nothing else surfaces that
Authorization on the clock12A deadline the plan does not control
Medication adherence below 80% PDC10Precedes the admission it causes
High risk and in no programme10Eligible and unenrolled is a decision nobody made
Worked example — the member at the top of the panel
PointsSignalWhy
40unworked_gaps5 open gaps with nothing running × 8
30riskhigh risk
10adherenceAtorvastatin 40mg adherence 62%, below the 80% threshold
80Total — John Doenext action: start an intervention on the HbA1c gap

The same member scores 85 on clinical risk and 80 on panel attention — two different questions with two different answers. Risk asks how likely a bad year is; the panel asks how much of that is unattended right now. A high-risk member with everything already in flight scores lower here, and should.

Next best action — expected value, and why the number is not on screen

app/services/nba.py · recommend()
The question

For this population: which member, which action, why them, why now, and who should do it?

The logic

Ranked by expected value — how much the gap matters, weighted by the member's risk, discounted by whether anything can actually be done today. A recommendation nobody can act on is noise, and the fastest way to lose a work queue is to fill it with noise. But the score itself never reaches the screen. The customer review's words were that numeric scoring is difficult for users to interpret, and that is not a cosmetic complaint: a recommendation justified by “32.0” is one nobody can argue with and — the part that matters — nobody can catch being wrong. So the arithmetic orders the list, and what a person sees is a band, the reasons with their sources, and the single next step.

The formula
impact = 10 × risk_weight × gap_priority_weight

  risk_weight          HIGH 1.6 · RISING 1.3 · MEDIUM 1.0 · LOW 0.8
  gap_priority_weight  Critical 2.0 · High 1.5 · Medium 1.0 · Low 0.6

score  = impact × 1.0   actionable today
       = impact × 0.9   previous outreach ended unreachable or declined
       = impact × 0.5   blocked — an authorization is pending

order  = band first (Critical, High, Medium), then score, then member

The band is derived from the facts, never from a cut point on the score, so the band and the reasons beneath it can never disagree:

  • Critical — a critical-priority gap; or a high or rising-risk member with a gap 90 days or more late
  • High — a high or rising-risk member, or a high-priority gap, or a gap 90 days or more late
  • Late means days past the gap's due date once it is overdue, and days since the gap opened while it is not yet due.
  • Medium — an open gap with no aggravating factor
Worked example — the top recommendation

Record HbA1c Control — band Critical, because a critical-priority gap on a high-risk member.

impact = 10 × 1.6 (HIGH risk) × 2.0 (Critical gap) = 32.0
score  = 32.0 × 1.0 (actionable now)          = 32.0   sort key only, never displayed

The eleven reasons the engine returned, each naming the system that asserted it — this is what the user actually reads:

Open quality gap — HbA1c ControlCare Gap
Overdue 97 daysCare Gap
Critical priority on the measure packageCare Gap
High-risk memberRisk
Impacts Diabetes: HbA1c Control (<8.0%) — a tracked VBC quality measure with a 75% targetQuality
1 ED visit in the last 6 monthsUtilization
Coverage not verified — check eligibility before bookingEligibility
No outreach attempted for this gap yetOutreach
Food insecurity recorded and unaddressed — may prevent attendanceSDOH
Enrolled in Complex Care ManagementProgram/Contract
Actionable nowWorkflow

Next step: “No prior authorization is required — this can be scheduled as soon as the member is reached.” That sentence is the answer from the authorization engine, not a guess made on this screen, which is why the two can never contradict each other.

The engine recommends: it creates nothing, transitions nothing and sends nothing. Each action is returned already marked enabled or not, with the reason when not — a control that looks live and then refuses teaches the user that the screen lies.

6 · Clocks and windows

Transition-of-care windows

app/services/transitions.py
The question

Somebody came out of hospital. How long do we have, and for what?

The logic

This is the highest-yield window in value-based care and the one most often missed, because the trigger arrives from outside — an ADT message, not a person's decision. Every window is measured from the discharge date, not from the day the notification arrived: a feed that runs a day late shortens the window, it does not extend it.

WindowLengthMeasured from
Contact after discharge48 hoursthe discharge date, not the day the notification arrived
Follow-up visit7 daysthe discharge date
Readmission counted in30 daysthe discharge date
A discharge still treated as actionable120 dayslookback horizon
Worked example

Discharged 2026-09-22, two days ago: contact due by 2026-09-24 — window still open — and the follow-up visit due by 2026-09-29. While that contact window is open it contributes 28 points to every related task in the queue and 25 to the member's panel score, which is how a discharge reorders somebody's morning without anyone re-prioritising anything by hand.

Prior-authorization decision clock

app/services/workflow.py · submit_pa() · app/core/config.py
The question

When is this decision due, and what does the platform do as that moment approaches?

The formula
decision_due = submitted_at + 72 hours   when urgency is expedited
             = submitted_at + 7 days     standard
Worked example

PA-2026-40188, standard urgency, submitted 2026-09-20 12:29 → decision due 2026-09-27 12:29. Exactly seven days, to the minute, from submission — not from receipt, and not from the start of the next business day.

Configurable per deployment and modelled on CMS-0057-F. Every queue in the product reads this same field, which is why an authorization nearing its deadline lifts its member's task without anybody re-prioritising anything. The API also refuses a denial that does not carry a specific reason code and text.

Engagement ladder, wait times and the contact ceiling

app/services/engagement.py
The question

How do we reach somebody who has not replied — without becoming the plan that harasses its members?

The logic

Channels escalate in order of intrusiveness and cost, each with a wait long enough for a real person to have replied. Voice is the last rung, not the first. Consent is checked by a single authority that every channel calls, and a refusal is written to the audit chain before it is raised — so a member who was not contacted still leaves a record of why they were not contacted.

Rung, in orderWait before the next
1 · SMS2 days
2 · Email3 days
3 · Portal7 days
4 · Voice2 days
LimitValue
Contacts allowed per member4
within a rolling window of7 days
Consent disclosure validity365 days
A ladder that stalls is itself a signal. Outreach that went out and came back with nothing contributes 14 points in both ranking engines, because it means the channel is wrong and no other screen would ever surface that.

7 · Programmes, providers and vendors

Care-management eligibility

app/services/enrollment.py · evaluate()
The question

Who should be offered care management, and what is the evidence for saying so?

The logic

Four named criteria, each recording its own evidence. The function returns the reasons, never a bare boolean — “eligible” with nothing behind it cannot answer an auditor, a medical director or the member. Before this existed, eligibility was a condition inline in the ranking engine that wrote nothing down, which meant eligible was a state no member could actually be in.

CriterionTestWhy the plan considers it
High-risk memberrisk tier is HIGHThe strongest predictor the plan has of the next twelve months
Rising risk with open gapstier RISING and three or more open care gapsRising risk is where care management is cheapest and most effective — before the admission, not after it
Recent acute eventan admission or ED visit within 180 daysThe strongest short-term predictor of the next admission is the last one
Unmet social barrierstwo or more unaddressed social needsNo clinical intervention reaches a member who cannot execute the plan
Worked example — six members qualify, on stated evidence
MemberCriterionEvidence recorded
John Doehigh_riskrisk tier HIGH
John Doerecent_acute1 acute encounter since 2026-03-28
Maria Gonzaleshigh_riskrisk tier HIGH
Sam Okaforrecent_acute2 acute encounters since 2026-03-28
Linda Tranrising_with_gaps4 open gaps, rising risk
Fatima Hassanrising_with_gaps4 open gaps, rising risk

Two members qualify on two criteria each. Note that Linda Tran and Fatima Hassan are not high risk — they are the rising-risk members the programme exists to reach early, and a HIGH-only rule would have missed both.

Deliberately not tunable from the screen. Who a plan enrols is clinical policy, and a threshold anybody can drag is a policy nobody has approved. ACCEPTED is the member's yes; ENROLLED is the plan's delivery — enrolment assigns a care manager and opens a care plan first, and a member who declined is never re-offered by an automated sweep.

Provider response metrics

app/services/opportunities.py · summary()
The question

We send gap-closure opportunities to attributed providers. Which of them engage, and how fast?

The formula
acceptance_rate%     = round(100 × accepted / responded)
disputed_rate%       = round(100 × disputed / responded)
evidence_returned%   = round(100 × with_evidence / all opportunities)
response_median_days = the middle value of the sorted response times
overdue              = past its due date and not yet settled
Two different denominators, deliberately. Acceptance is judged only against providers who answered — scoring silence as a refusal would misrepresent the ones still working the list, and a provider who has not replied yet is a different problem from one who declined. Evidence return is judged against every opportunity, because a claim closed without evidence is exactly what that denominator exists to count.
A provider's say-so never closes a gap. “Already completed” routes to the measure engine, which looks for the result; if the result is not there the gap stays open and the disagreement becomes visible, instead of resolving silently in the plan's favour — or the provider's.
Worked example — and what an empty rate means

Dr. P. Osei currently holds 2 opportunities and has answered neither, so acceptance rate reads as a dash, not 0%. That distinction is the formula doing its job: zero would be a statement about this provider's judgment, and the honest statement is that there is nothing to divide by yet.

Referral SLA

app/services/referrals.py · sla_days()
The question

We sent this member to somebody else. When should we have heard back, and who finds out if we have not?

The formula
due_on   = today + sla_days(queue)
sla_days = the tenant's setting for that queue, else its default, else 3 days

Per queue, because one queue's standard is not another's — hard-coding three days for all of them is the assumption a customer's operations team overturns on day one. A redirect restarts the clock for the new queue and does not settle the referral; unable to contact does settle it, because a team that tried and could not reach somebody has produced a result worth keeping rather than an absence. The originator is notified on every advance, and a confirmed outcome is appended to the linked intervention's own trail, so the loop visibly closes where it started.

8 · Looking forward

Trends and projection

app/services/trends.py · forecast()
The question

On the current trajectory, where does this metric land at the end of the performance period?

The logic

Ordinary least squares through the captured points — deliberately the simplest method that can be checked by hand, because a projection is arithmetic and a forecast is a claim, and this produces the first. What makes it trustworthy is not the fit but the refusals: the function would rather return a sentence than a number it cannot stand behind.

The formula
method    = ordinary least squares through the captured points
slope     = Σ(x − x̄)(y − ȳ) / Σ(x − x̄)²
intercept = ȳ − slope × x̄
R²        = 1 − ss_res / ss_tot
projected = slope × (days to the horizon) + intercept

And the three refusals, each returned with its reason in words rather than a number nobody should trust:

  • Fewer than three captured points, or a span under 14 days → no projection. Two points define a line through anything.
  • R² below 0.25 → no projection, with the R² stated. A poor straight-line fit means the slope is noise from a single week, not a trend.
  • No open performance period → no projection: there is no horizon to project to. Any result outside a metric's natural bounds is clamped and flagged — a percentage projecting past 100 is arithmetic escaping its own units.
Worked example — ten metrics, nine projections and one refusal
MetricR²Projected at 2026-12-31
Quality composite0.97751.16%
Cost PMPM (allowed)0.9761,361.69
Open care gaps0.9619.68
Measure: HbA1c control0.97863.21%
Measure: retinal exam1.00.0%
High and rising-risk members0.8930.93
Attributed members—refused: only 1 captured point

Two rows worth reading together. The retinal measure fits perfectly (R² = 1.0) and projects 0.0% — a flat line through a measure where nothing has ever closed is an excellent fit and a useless forecast, which is why a high R² is a licence to draw the line, not evidence that the future will follow it. And attributed members refuses outright on a single point rather than drawing a line through it.

Note the honest tension on this screen: the composite projects to 51.16% against a 60% gate. The projection says the current trajectory does not clear the gate — and the gate path above says twelve gap closures would. Those two numbers answer different questions, and the screen shows both.

9 · Limits and conventions

Rounding, and what deliberately has no formula

QuantityRounding
Rates and composites1 decimal
Composite points2 decimals
Money2 decimals
Members needed to reach a targetwhole, rounded up
Percentages of a provider's responseswhole
  • Rounded once, at the end — with two stated exceptions. A displayed figure is a rendering of the exact value, never an input to the next step — otherwise two screens reporting one quantity drift apart by a hundredth, and the customer checking the arithmetic is the one who finds it. The exceptions are deliberate, so that a customer can reproduce a number from the figures printed beside it: the quality composite is the mean of the measure rates as displayed (each already to one decimal), and gross savings is the displayed per-member variance (to the cent) times the displayed member-months. Every screen that shows either number computes it the same way, in one function.
  • No learned model sits behind any of this. Every rule above is deterministic, versioned and readable.
  • Scores are computed at read time — no nightly job can drift from what the screen shows.
  • Nothing here decides. The server applies its own guards regardless of what the screen suggested.
What these numbers are not
  • The measure packages are synthetic. Real HEDIS and Stars specifications are licensed content with weights and cut points this engine does not implement. Every rate in this paper is correct arithmetic over a synthetic specification.
  • The risk weights are clinically reasoned, not empirically fitted. They encode conventional thresholds — 80% PDC, 140 systolic, 8.0% HbA1c — and their value is that they are arguable. A plan that disagrees with a weight can say so, and the version string makes the change visible in the score history.
  • Settlement figures are estimates. The payer reconciles against run-out claims months later.
  • A projection is not a forecast, and an unweighted composite is a scoring method chosen by a contract rather than a fact about quality.
How this paper stays true. Every constant published here is read back out of this page by an automated check and compared with the constant that actually computes it — risk weights and tier bands, lab thresholds, the utilisation caps, PDC, both ranking tables, the expected-value weights, every clock, the ladder and its ceiling. If a weight changes in the code and this page is not updated in the same change, the check fails and names the row. The honest limits above are asserted the same way, because removing one is a compliance change rather than an edit.

Part II · Every number on every screen

Point at any number and find how it was made

Part I explains the calculations that drive the product. This part is the dictionary: 444 values across 25 screens, each with the formula in plain words, why it is computed that way, the function that computes it, and its value in the demonstration tenant. Those demo values are not typed by a person: they are read from the product itself, on a freshly reset demo tenant, by signing in as the persona who sees the screen and calling the same endpoint the screen calls. The demo tenant is reset every day, and this page's values are regenerated on the server from that day's reset, so they match what the demo shows today: values as of 2026-09-24.

Command Center

18 values

The VBC manager, the executive and the platform administrator open this screen as their Dashboard (the care manager's Dashboard is a different screen). It shows six population counts, each linking to the list behind it, and today's priorities: groups of members who share a reason to act now.

Members

KPI card 1, top row

Formula

count of every Patient row in the tenant no attribution filter, no status filter, no panel filter

Why this wayIt answers 'how many people are on file', not 'how many we are responsible for'. That costs a mismatch with Contract Health, whose rows are built over the members attributed in the current contract period, so the two screens can show different member totals on the same day. For a panelled login (a provider) every card counts that login's panel only.

In the demo13 members

Check it yourselfClick the card: it opens Members, and that list has the same number of rows.

app/services/command_center.py · kpis()app/static/app.js · rCommandCenter()

High / rising risk

KPI card 2, top row

Formula

count of Patient rows whose stored risk_tier, upper-cased, is HIGH or RISING over every member in the tenant

Why this wayIt reads the tier stored on the member record, which Part I (Clinical risk score and tier) calls a convenience: the score history is the authority, and the tier bands (HIGH 60 or more, RISING 35 or more) are defined there. It is not re-scored at read time. Contract Health's 'High / rising risk members' uses the same two tiers but only over attributed members, so the two can differ; the care manager's Dashboard card of the same name matches the tier without upper-casing. The server also marks this card amber when the count is above zero, but the screen draws a hint only for red, so the amber flag is never shown.

In the demo6 members

Check it yourselfOpen Members and count the HIGH and RISING tier badges; the total matches the card.

app/services/command_center.py · kpis()app/services/risk.py · score_patient()

Open care gaps

KPI card 3, top row

Formula

count of CareGap rows where gap_is_open(status) open = status not in CLOSED, EXCLUDED, NOT_APPLICABLE, GAP_CLOSED, RESOLVED, DUPLICATE; a blank status counts as open

Why this wayGaps being worked (IN_PROGRESS, SCHEDULED, AWAITING_AUTH, SERVICE_COMPLETED, REOPENED) still count, because a gap is open until it is closed, not until someone touches it. It counts gap rows, not members, over every member and every measure. Contract Health's 'Open care gaps' counts only attributed members' gaps, so it can be lower. The care manager's Dashboard counts gaps whose status is in the open list; the two agree unless a gap has a blank or unknown status, which this card counts as open and the Dashboard does not.

In the demo29 gaps

Check it yourselfClick the card: Care Gaps opens with the same total in its header, computed by the same gap_is_open rule.

app/services/command_center.py · kpis()app/services/workflow.py · gap_is_open()

Discharges (30d)

KPI card 4, top row

Formula

the Transitions list's inpatient discharges whose discharge date is 0 to 30 days before today (US business date)

Why this wayOne definition of a discharge: the card is the last 30 days of the very list it opens (inpatient stays, never a future date), so the card and the list behind it cannot disagree. It counts discharges, not members. For a provider login it counts the provider's panel only.

In the demo1 discharges

Check it yourselfClick the card: Transitions lists the inpatient discharges; count the rows discharged in the last 30 days.

app/services/command_center.py · kpis()app/services/transitions.py · recent_discharges()

Active interventions

KPI card 5, top row

Formula

count of Intervention rows whose status is not CANCELLED, COMPLETED, GAP_CLOSED, DECLINED or UNREACHABLE

Why this wayDeclined and unreachable interventions are treated as finished, because nobody is working them. The Interventions list the card opens uses the same five states, so card and list agree. The care manager's Dashboard shows a card with the same label that excludes only GAP_CLOSED, CANCELLED and COMPLETED, so it counts declined and unreachable interventions as active and can be higher.

In the demo1 interventions

Check it yourselfClick the card: Interventions opens with the same total in its header.

app/services/command_center.py · kpis()app/services/worklists.py · interventions()

Tasks overdue

KPI card 6, top row

Formula

count of WorkTask rows whose status is open (any case) and whose due date is before today today = the US business date, not the server's date; every owner in the tenant, assigned or not

Why this wayA task due today is not overdue until tomorrow. The count is tenant-wide, but the card opens My Work, which starts on the 'Mine' tab: a manager who owns no tasks sees an empty list behind a non-zero card until they switch to 'Team'. The care manager's Dashboard 'Overdue tasks' card uses the same date rule.

In the demo8 tasks

Check it yourselfClick the card, choose 'Team' in My Work, and count the rows marked OVERDUE.

app/services/command_center.py · kpis()app/core/clock.py · business_today()

'needs attention' hint under a KPI card

KPI cards, under the number

Formula

shown when the card's tone is red; only Tasks overdue sets red, when its count is above 0

Why this wayOnly a breached commitment (work already past due) earns the warning. The server also sends amber for High / rising risk, but the screen ignores amber, so that card never shows a hint.

In the demoTasks overdue

Check it yourselfThe hint sits under Tasks overdue and disappears when that count is 0.

app/services/command_center.py · kpis()app/static/app.js · rCommandCenter()

Today's priorities (count)

Today's priorities panel, number beside the title

Formula

number of clusters returned by the six detectors (at most 6) a detector with nobody in it is left out, not shown as 0

Why this wayAn empty row would read as a metric; its absence reads as 'nothing to do'. The cost is that a detector that raises an error is also skipped silently, so a missing row can mean 'nothing to do' or 'the check failed', and the screen cannot tell the two apart.

In the demo4 clusters

Check it yourselfCount the table rows; it matches the number beside the title.

app/services/command_center.py · priorities()app/static/app.js · rCommandCenter()

Priority (CRITICAL / HIGH / MEDIUM)

Today's priorities table, Priority column

Formula

fixed per detector, not scored: CRITICAL = post-discharge follow-up, assessments overdue · HIGH = measure below target, vendor referrals overdue · MEDIUM = duplicate outreach, members we cannot reach colour: CRITICAL red, HIGH amber, anything else grey

Why this wayThe priority says which kind of clock is running: a window that cannot be reopened (Critical), a commitment already breached (High), or waste that breaches nothing (Medium). It does not grow with the size of the cluster; one member in a closing window outranks a hundred with a missed target.

In the demoHealth risk assessments overdue CRITICAL, Post-discharge follow-up CRITICAL, Diabetic Retinal Exam HIGH, Duplicate outreach MEDIUM

app/services/command_center.py · priorities()app/static/app.js · rCommandCenter()

Row order ('worst clock first')

Today's priorities table, top to bottom

Formula

sort by priority (Critical 0, High 1, Medium 2), then by member count, largest first

Why this wayThe kind of deadline decides first; size only breaks ties within the same priority. It is not the Work-queue score in Part I (Work-queue ranking): no member-level signal enters it.

In the demoHealth risk assessments overdue, Post-discharge follow-up, Diabetic Retinal Exam, Duplicate outreach

Check it yourselfRead the Priority and Members columns: priorities never go up as you read down, and within one priority the member counts never go up.

app/services/command_center.py · priorities()

Members (per cluster)

Today's priorities table, Members column (bold number)

Formula

number of distinct members in the cluster

Why this wayThis counts people. The 'Why' text beside it often counts something else (encounters, assessment rows, referrals, outreach ladders), so the two numbers on one row can differ and are not meant to match. For a panelled login the members are narrowed to the panel before the count and the example names are taken, so a provider sees only its own members.

In the demoHealth risk assessments overdue 2, Post-discharge follow-up 1, Diabetic Retinal Exam 6, Duplicate outreach 3 members

Check it yourselfClick the row's action button; the list it opens contains these members, among others.

app/services/command_center.py · priorities()app/api/population.py · command_center()

Example names under the member count

Today's priorities table, Members column (small text)

Formula

names of the first 3 members of the cluster, ordered by internal member id … is added when the cluster has more than 3 members

Why this wayThe names are there to make a cluster concrete, not to rank anyone: the order is the internal id order, which is arbitrary and changes on every demo reset, so the three names shown for a large cluster are not the most urgent three. The demo value below therefore reports how many names are shown rather than which. Like the count, the names are chosen before the panel filter runs (reported as a defect).

In the demoHealth risk assessments overdue 2 name(s), Post-discharge follow-up 1 name(s), Diabetic Retinal Exam 3 name(s) + …, Duplicate outreach 3 name(s)

app/services/command_center.py · priorities()app/static/app.js · rCommandCenter()

Post-discharge follow-up: 'N inside the 2.0-day contact window, M inside the 7-day visit window'

Today's priorities table, Why column

Formula

E = encounters of any kind discharged 7 days ago or less (transitions.VISIT_DAYS = 7) N = those discharged 2 days ago or less (CONTACT_HOURS 48 / 24 = 2.0) M = E - N, i.e. discharged 3 to 7 days ago · members = distinct members in E

Why this wayThe windows are the ones in Part I (Transition-of-care windows), imported from the transitions module rather than restated. N and M count encounters, not members. 'M inside the 7-day visit window' excludes the N rows, although those are inside the visit window too. The contact window prints as '2.0-day' because it is computed by dividing hours by 24. When N is 0 the text reads only 'E inside the 7-day visit window'. Unlike the Transitions screen, this counts any encounter kind, not only inpatient stays.

In the demo1 inside the 2-day contact window, 0 inside the 7-day visit window

Check it yourselfClick View transitions: the contact window is still open on each discharge counted in N.

app/services/command_center.py · _post_discharge()

Measure below target: 'X% against a Y% target, Z points short'

Today's priorities table, the High row named after a measure

Formula

for each measure with a target: rate = 100 × (gap rows not open) / (all gap rows for that measure); short = target - rate the measure with the largest positive short is shown; X, Y and Z are rounded to whole numbers members = members with an OPEN gap on that measure

Why this wayOne measure is reported, not all of them, so the row is a decision rather than a report. This is NOT the measure rate in Part I (A measure across a population), which is compliant members over eligible members. Here only members who have ever had a gap row are counted: a compliant member who never had a gap is missing from both sides, gaps closed as EXCLUDED or NOT_APPLICABLE count as met, and retired measures are not filtered out. The Quality screen and this row can therefore show different rates for the same measure; in the reset demo tenant they do.

In the demoDiabetic Retinal Exam: 0% against a 70% target — 70.0 points short

Check it yourselfOpen Quality and find the same measure: its rate is computed over the eligible population and will usually differ from this one.

app/services/command_center.py · _measure_below_target()app/services/quality.py · evaluate_tenant()

Health risk assessments overdue: 'N past their contractual window'

Today's priorities table, Why column

Formula

N = Assessment rows whose status is not COMPLETED, REFUSED or UNREACHABLE and whose due_on is before today members = distinct members among them

Why this wayAn assessment the member refused, or that could not be completed because the member was unreachable, is settled rather than late. N counts assessment rows of any kind, not only HRAs. The due date is set when the assessment is raised: 90 days after enrolment for the first, then 365 days after the last settled one. This screen does not raise missing assessments itself, so a member enrolled since the last sweep is not counted until one exists. The sentence 'the denominator is every enrolled member' describes the HRA completion-rate KPI, which the product computes elsewhere but no screen currently shows; this row computes no rate and no denominator, only a count.

In the demo2 past their contractual window

app/services/command_center.py · _assessments_overdue()app/services/assessments.py · ensure_due()

Vendor referrals overdue: 'N past their contracted SLA, the worst is <vendor> at D days over'

Today's priorities table, Why column

Formula

late = VendorReferral rows whose sla_due_on is before today and whose status is not CONFIRMED, DECLINED, FAILED or CANCELLED N = number of late referrals · D = the largest today - sla_due_on in days, with that referral's vendor

Why this wayIt calls the same is_overdue rule as the vendor scorecard, so this row and Vendor Performance cannot disagree about which referral is late. N counts referrals; the Members column counts members. The row is absent when nothing is late.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNothing is late in the freshly reset demo tenant, so the row does not appear. When it does, click Vendor performance: the breaches there are the same referrals.

app/services/command_center.py · _vendor_sla()app/services/vendors.py · is_overdue()

Duplicate outreach: 'N member(s) have more than one live outreach ladder, one has K'

Today's priorities table, Why column

Formula

live ladder = OutreachSequence whose status is not COMPLETED, EXHAUSTED or STOPPED N = members with 2 or more live ladders · K = the most live ladders any one member has

Why this wayIt flags members being contacted about several things at once, which the member experiences as being pestered. The text says the ladders come 'from separate programmes', but the code groups by member only and does not check the programme or purpose of each ladder (Part I, Engagement ladder, describes one ladder at a time).

In the demo3 member(s) have more than one live outreach ladder — one has 3

Check it yourselfOpen any listed member's Engagement tab: more than one ladder is live.

app/services/command_center.py · _duplicate_outreach()

Members we cannot reach: 'N outreach ladder(s) have run out of usable channels'

Today's priorities table, Why column

Formula

N = OutreachSequence rows with status EXHAUSTED, with no time limit members = distinct members among them

Why this wayA ladder is exhausted when every usable channel has been tried (Part I, Engagement ladder, wait times and the contact ceiling). N counts ladders; the Members column counts members. Because there is no time limit, a ladder that ran out months ago still counts. This is a different signal from the 'outreach stalled' ranking factor, which reads intervention status.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo ladder is exhausted in the freshly reset demo tenant, so the row does not appear. It appears once a ladder has used every channel.

app/services/command_center.py · _unreachable()

Dashboard

31 values

The Care Manager's "what needs me today" screen (payer personas — VBC Manager, Executive, Platform Admin — get the Command Center under the same menu item instead): eight operational counts, the top of the ranked member panel, three progress strips and the population totals.

Tasks due today

Operational card row, first card

Formula

count of work tasks with status open whose due date (first 10 characters, YYYY-MM-DD) equals today's US business date. Tenant-wide: every owner, not only mine

Why this wayRead against the US business calendar (business_today()), not the server's time zone, so a task due today does not jump to tomorrow at 19:00 Eastern. It counts tasks, not members: one member with three tasks due today adds three. It is a subset of Population "Open tasks".

In the demo16 tasks

Check it yourselfMy Work, Team: sort by Due and count the open tasks dated today.

app/api/vbc.py · summary()

Overdue tasks

Operational card row, second card (red badge when above 0)

Formula

count of work tasks with status open that have a due date and whose due date (due[:10]) is earlier than today's US business date, compared as ISO text. Tenant-wide

Why this wayA task due today is not overdue until tomorrow. The Command Center's "Tasks overdue" card computes the same idea in a second function (command_center.py · kpis()): it lower-cases the status and parses the date, so a task stored as Open or with a malformed due value is counted on one screen and not the other. On the demo data both show the same number. See Part I, Prioritisation, for how overdue work is ranked.

In the demo8 tasks

Check it yourselfMy Work, Team: sort by Due and count the open tasks dated before today; as VBC Manager, the Command Center's "Tasks overdue" card shows the same number on the demo.

app/api/vbc.py · summary()app/services/command_center.py · kpis()

High / rising risk

Operational card row, third card (never badged)

Formula

count of members whose stored risk tier is exactly HIGH or RISING. Tenant-wide, every member row including unresolved duplicates

Why this wayThe tier is the one stored by the last rescore (see Part I, Population health), not recomputed on read. It equals the High plus Rising bars of the Risk distribution strip on this page. The Command Center card of the same name upper-cases the tier first, so it would also count a tier stored as high; the demo stores upper case and both screens agree.

In the demo6 members

Check it yourselfAdd the High and Rising numbers in the Risk distribution strip lower on the page.

app/api/vbc.py · summary()app/services/risk.py · score_patient()

Discharges to follow up

Operational card row, fourth card

Formula

count of inpatient encounters discharged 0 to VISIT_DAYS = 7 days before today (visit window still open). Counts encounters, not distinct members

Why this wayAnchored to the discharge date, so a discharge we learned about late has less window left, not a fresh one (see Part I, Clocks and windows). It counts every discharge inside the 7-day window whether or not the contact or the visit has already happened: nothing checks a task or an appointment, so the label says more than the count proves. The Transitions screen's "Visit window open" count is the same computation. The Command Center's "Discharges (30d)" is a different number: any encounter kind, 30 days.

In the demo1 discharges

Check it yourselfOpen Transitions: the "Visit window open" card shows the same number.

app/api/vbc.py · summary()app/services/transitions.py · recent_discharges()

PA / eligibility blocking care

Operational card row, fifth card

Formula

(authorizations with status SUBMITTED, IN_REVIEW or PENDED) + (members with at least one open gap and no coverage row that is active with effective_from ≤ today ≤ effective_to)

Why this wayBoth causes stop the same service, so they share one card; the note under Care gap progress splits them. The units are mixed: authorizations plus members. A member with two pending authorizations, or a pending authorization and no coverage, adds two or more. The authorization half is tenant-wide and does not check the decision timestamp, while the panel's "authorization on the clock" signal also requires that no decision is recorded.

In the demo1

Check it yourselfIt equals the sum of the four numbers in the "Blocking care" note under Care gap progress.

app/api/vbc.py · summary()

Outreach needing follow-up

Operational card row, sixth card

Formula

count of interventions whose status is OUTREACH, UNREACHABLE or DECLINED. No age condition. Counts interventions, not members

Why this wayThe same three statuses as the "Outreach stalled" signal in Part I, Prioritisation. Two limits: an outreach started a minute ago counts, because no waiting time is applied; and a member who answered and DECLINED counts, although the paper describes the signal as "sent, nothing back".

In the demo0 interventions

Check it yourselfMy Panel, "all factors": members with an outreach_stalled factor are where these sit; one member can hold several such interventions.

app/api/vbc.py · summary()

Active interventions

Operational card row, seventh card (never badged)

Formula

count of interventions whose status is not GAP_CLOSED, CANCELLED or COMPLETED

Why this wayAnything not finished counts as active here, including DECLINED and UNREACHABLE. The Command Center card with the same label, and the Interventions worklist it opens, both leave out DECLINED and UNREACHABLE, so this card shows a larger number than those screens as soon as one outreach fails. On the fresh demo there is one intervention (AUTH_APPROVED) and all three show 1.

In the demo1 interventions

Check it yourselfAs VBC Manager, the Command Center's "Active interventions" card and its Interventions worklist show the same number while no outreach has ended declined or unreachable.

app/api/vbc.py · summary()app/services/command_center.py · kpis()app/services/worklists.py · interventions()

Gaps closed this month

Operational card row, eighth card

Formula

count of care gaps with status CLOSED and a closure date (closed_on, YYYY-MM-DD) on or after the first day of the current US business month

Why this wayOnly CLOSED counts; EXCLUDED, NOT_APPLICABLE and the other closed states do not. Gaps closed before closure dates were recorded have no date and are reported separately (see "Gaps closed without a closure date") instead of being counted as not this month, which would read as a measured zero. Reopening a gap clears its closure date.

In the demo0 gaps

Check it yourselfMember 360, Care gaps: a gap closed this month shows Closed with this month's closure date; the fresh demo has none closed yet.

app/api/vbc.py · summary()

"needs attention" badge

Under five of the eight operational cards

Formula

shown when the card's value is above 0, on Tasks due today (amber), Overdue tasks (red), Discharges to follow up, PA / eligibility blocking care and Outreach needing follow-up (amber). Never shown on High / rising risk, Active interventions or Gaps closed this month

Why this wayA plain non-zero test with no threshold: the five badged cards are things to do, the other three are things to know. Red is kept for work already past its date.

In the demoTasks due today, Overdue tasks, Discharges to follow up, PA / eligibility blocking care

Check it yourselfEvery badged card shows a number above 0; the unbadged ones never carry the badge.

app/static/app.js · rDash()

Today's priorities (count beside the title)

Today's priorities panel, title

Formula

number of rows shown = the smaller of 8 (the screen asks for limit=8) and the number of members scoring above 0

Why this wayThe same ranking as My Panel (see Part I, Prioritisation, My Panel), cut at 8 so the first screenful stays short. My Panel asks for 25, so the two screens show different row counts from one ranking.

In the demo8 members

Check it yourselfCount the table rows; the first 8 rows of My Panel are the same members in the same order.

app/static/app.js · rDash()app/api/population.py · my_panel()app/services/panel.py · panel()

of N needing attention

Today's priorities panel, right of the title

Formula

number of members visible to the viewer whose panel score is above 0 (all of them, not only the 8 shown)

Why this wayVisible means the whole tenant for a care manager and a provider's attributed members for a provider. It is not "my" members: the ranking function's description says it narrows to the members a care manager owns work for, but the code only uses the viewer to set a mine flag, so every care manager sees the same N.

In the demo12 members

Check it yourselfMy Panel lists the same members (up to 25 rows); for the demo's 12 the whole list is on one page.

app/services/panel.py · panel()app/core/scope.py · visible_ids()

N with no task

Today's priorities panel, right of the title

Formula

of the members scoring above 0, the number with zero work tasks in status open (any owner)

Why this wayCounts across all scored members, not only the 8 rows shown, so it can be larger than the number of NO TASK badges visible on this page. These are the members the task queue cannot show, because nobody has raised work for them.

In the demo2 members

Check it yourselfOn My Panel, count the rows with a NO TASK badge.

app/services/panel.py · panel()

Priority

Today's priorities table, Priority column (shown for John Doe)

Formula

sum of signal points (see Part I, Prioritisation, My Panel for the table); rows sorted by score, highest first, ties by member name; members scoring 0 are not listed

Why this wayThree differences between the code and Part I's table. The 25 points apply while the 7-day VISIT window is open, not only the 48-hour contact window, and the extra 15 is added from day 3, so days 3 to 7 score 40. The 10 for "high risk and in no programme" is given only when enrollment is the member's top next-best action, so a high-risk unenrolled member whose top action is a gap gets nothing. Adherence reads a missing or 0.0 PDC as adherent (the risk score reads 0.0 as non-adherent). A declined or unreachable intervention counts its gap as in flight (2), not unworked (8).

In the demo80 points

Check it yourselfMy Panel, open "all factors" on John Doe: +40 + 30 + 10 = 80, Part I's worked example.

app/services/panel.py · panel()

Reason

Today's priorities table, Reason column (shown for John Doe)

Formula

the explanation text of the member's largest score component. Components are sorted by points, highest first; a tie keeps the order risk, unworked gaps, in flight, discharge, contact missed, adherence, authorization, stalled outreach, coverage, programme

Why this wayOne line, not all of them, so the column stays readable; the full list is on My Panel. For John Doe the 40-point unworked-gaps component outranks the 30-point risk tier. Two text limits: adherence percentages are truncated, not rounded (int(pdc×100)), and a stalled outreach reads "reached out, nothing back" even when the member answered and declined.

In the demo5 open gap(s) with nothing running: HbA1c Control — Glycemic Assessment, Diabetic Retinal Exam, Statin Adherence (PDC >= 80%)

Check it yourselfMy Panel, "all factors": the Reason is the text of the factor with the most points.

app/services/panel.py · panel()

NO TASK badge

Today's priorities table, Member column

Formula

shown when the member has zero work tasks in status open, whoever owns them

Why this wayIt says only that no task exists. It says nothing about interventions: a member can carry NO TASK while a gap is being worked.

In the demoJon Doe

Check it yourselfOpen the member's 360: the Tasks list has no open task.

app/services/panel.py · panel()

"X risk" under the member name

Today's priorities table, Member column

Formula

the member's stored risk tier, from the rules engine: HIGH ≥ 60 points, RISING ≥ 35, MEDIUM ≥ 15, else LOW (see Part I, Population health)

Why this wayIt is the tier as of the last rescore, not recomputed on this read. A tier can also be set by hand through the records interface; the demo's duplicate record "Jon Doe" is seeded LOW after the tenant rescore, with no factors behind it.

In the demoHIGH

Check it yourselfMember 360, Risk: the factors listed add up to a score inside that tier's band.

app/services/risk.py · score_patient()app/services/risk.py · rescore()

Recommended action, grey reason line

Today's priorities table, under the Recommended action (shown for John Doe)

Formula

the first three reason texts of that action joined with ; , then cut to the first 90 characters in the browser

Why this wayThree reasons, not the full list of up to eleven, because the full list lives on the member 360. The cut is a plain character count, not a word boundary: John Doe's line is 91 characters, so the screen ends "…measure packag".

In the demoOpen quality gap — HbA1c Control; Overdue 97 days; Critical priority on the measure packag

Check it yourselfMy Panel shows the same reason uncut, and the member 360's Next best action lists the reasons in the same order.

app/services/nba.py · _recommend_for_gap()app/static/app.js · rDash()

Due

Today's priorities table, Due column (shown for John Doe)

Formula

the earliest date among: each open task's due date, the discharge follow-up visit date (discharge + 7 days), and each pending authorization's decision deadline (converted to the US business date). The reason text of that date is printed under it; no deadline when there is none

Why this wayThree different clocks can be running, so the reason travels with the date (see Part I, Clocks and windows, for the discharge and authorization clocks). The earliest date may already be past: for John Doe it is a task 97 days overdue. The discharge date is added for any inpatient discharge in the last 120 days, even after its 7-day window closed, and with two such discharges the older one is used.

In the demo97 days before the reset

Check it yourselfMember 360: the earliest date among the member's open tasks, discharge window and pending authorizations is the one shown.

app/services/panel.py · _soonest_due()app/services/panel.py · panel()

Care gap progress: Open / In progress / Closed

Care gap progress strip (Closed is also the Population "Gaps closed" card)

Formula

Open = gaps with status OPEN or REOPENED; In progress = IN_PROGRESS, SCHEDULED or AWAITING_AUTH; Closed = CLOSED only

Why this waySERVICE_COMPLETED (the member attended, evidence not yet in) is an open state but sits in neither bar, so Open plus In progress can be smaller than the Population "Open care gaps" card on the same page. EXCLUDED, NOT_APPLICABLE and the other closed states are not shown either. On the fresh demo no gap is in those states and the strip adds up to 29. Closed counts gap rows, not the measure numerator in Part I, Quality measurement.

In the demoOpen 28, In progress 1, Closed 0

Check it yourselfOpen + In progress equals Population "Open care gaps" whenever no gap is waiting on evidence.

app/api/vbc.py · summary()

Care gap progress bar lengths

Care gap progress strip, the three bars

Formula

bar length = round(100 × value ÷ (Open + In progress + Closed)) %, or 0% when that total is 0. Whole percent

Why this wayEach bar is a share of the three states drawn, not of all gaps, so gaps in states outside the bars do not shorten them. Whole-percent rounding can make the bars total 99% or 101%.

In the demoOpen 97%, In progress 3%, Closed 0%

Check it yourselfDivide each count by the sum of the three and round.

app/static/app.js · rDash()

Blocking care: authorization(s) on the clock

Note under Care gap progress (shown only when PA / eligibility blocking care is above 0)

Formula

count of prior authorizations with status SUBMITTED, IN_REVIEW or PENDED. Tenant-wide; counts authorizations, not members

Why this wayAn authorization still on its clock stops the member being scheduled, so it belongs on an operational board (see Part I, Clocks and windows, Prior-authorization decision clock). It does not check whether a decision timestamp is already recorded, while the panel's member signal does.

In the demo0 authorizations

Check it yourselfAuthorizations screen: count the rows at Submitted, In review or Pended.

app/api/vbc.py · summary()

Blocking care: no coverage on file / terminated / lapsed

Note under Care gap progress

Formula

for each member with at least one open gap and no coverage row that is active with effective_from ≤ today ≤ effective_to: no coverage rows at all → no coverage on file; otherwise any row not active → terminated; else → lapsed

Why this wayEach member lands in exactly one bucket, because the three causes need different fixes: a missing record is often an unmerged duplicate, terminated means the member left the plan, lapsed means the dates ran out. Members with no open gap are skipped: nothing is waiting on cover. The same test drives the panel's 18-point "no active coverage" signal.

In the demono coverage on file 1, terminated 0, lapsed 0

Check it yourselfThe demo's 1 is Jon Doe, the unmerged duplicate of John Doe: his 360 has no coverage.

app/api/vbc.py · summary()

Gaps closed without a closure date

Note under Care gap progress (shown only when above 0)

Formula

count of care gaps with status CLOSED and no closure date

Why this wayThe closure date was added later, so older closures carry none. They are counted as closed but left out of "this month" and named here, rather than silently treated as closed in some other month.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo gap is closed in the fresh demo, so the note does not appear. It appears once a gap with status Closed has no closure date, for example a record migrated from before closure dates existed.

app/api/vbc.py · summary()

Engagement funnel: Outreach / Reached / Scheduled / Completed

Engagement funnel strip

Formula

each stage = interventions whose status is at or past that stage in the order IDENTIFIED, ASSIGNED, OUTREACH, ENGAGED, AWAITING_AUTH, AUTH_APPROVED, SCHEDULED, IN_PROGRESS, COMPLETED, GAP_CLOSED, leaving out UNREACHABLE, DECLINED and CANCELLED. Outreach ≥ OUTREACH, Reached ≥ ENGAGED, Scheduled ≥ SCHEDULED, Completed ≥ COMPLETED

Why this wayStages are cumulative, so a member already scheduled still counts as reached. Consequences: an intervention waiting on or holding an authorization counts as reached but not scheduled; one that was reached and later declined drops out of every stage; a REOPENED intervention is in none. It counts interventions, not members.

In the demoOutreach 1, Reached 1, Scheduled 0, Completed 0

Check it yourselfThe demo's one intervention is at Auth approved: it counts in Outreach and Reached only.

app/api/vbc.py · summary()

Engagement funnel bar lengths

Engagement funnel strip, the four bars

Formula

bar length = round(100 × stage ÷ Outreach) %, dividing by 1 when Outreach is 0. Whole percent

Why this wayEach stage is shown as a share of the Outreach stage, so Outreach is always a full bar when it is above 0 and the others read as conversion from it.

In the demoOutreach 100%, Reached 100%, Scheduled 0%, Completed 0%

Check it yourselfDivide each stage by the Outreach number and round.

app/static/app.js · rDash()

Risk distribution: High / Rising / Medium / Low

Risk distribution strip

Formula

count of members by stored risk tier; a tier with no members shows 0. Tenant-wide

Why this wayThe tier as of the last rescore (see Part I, Population health). It includes hand-set tiers and the demo's unmerged duplicate "Jon Doe", seeded LOW with no factors, so the Low bar holds one member no rule scored.

In the demoHigh 2, Rising 4, Medium 5, Low 2

Check it yourselfThe four numbers add up to Population "Patients"; Patients screen, filter by risk.

app/api/vbc.py · summary()app/services/risk.py · score_patient()

Risk distribution bar lengths

Risk distribution strip, the four bars

Formula

bar length = round(100 × tier count ÷ total) %, where total sums every tier returned, including any member with no tier, which is not drawn. Whole percent

Why this wayA member with no tier shortens all four bars without appearing, so the bars can total under 100%; rounding alone can make them total 99% or 101%. The demo totals 99%.

In the demoHigh 15%, Rising 31%, Medium 38%, Low 15%

Check it yourselfDivide each tier count by Population "Patients" and round.

app/static/app.js · rDash()

Patients

Population panel, first card

Formula

count of every member record in the tenant

Why this wayNot attributed members and not de-duplicated: an unmerged duplicate (the demo's "Jon Doe") counts as a second member. The whole tenant for a care manager; a provider who opens this screen sees the count of their own attributed panel, like every other figure on it.

In the demo13 members

Check it yourselfThe Patients screen lists the same number of rows; the Command Center's "Members" card agrees.

app/api/vbc.py · summary()

Open care gaps

Population panel, second card

Formula

count of care-gap rows whose status is in the open set OPEN, IN_PROGRESS, SCHEDULED, AWAITING_AUTH, SERVICE_COMPLETED, REOPENED

Why this wayGap rows, including gaps already scheduled or attended and waiting on evidence. Part I, Quality measurement defines open gaps per measure as denominator minus numerator and totals twenty-nine; this card shows 29 on the demo too. The Command Center card counts any status not in the closed set, which agrees for every known status.

In the demo29 gaps

Check it yourselfAs VBC Manager, the Command Center's "Open care gaps" card and the Open care gaps worklist behind it show the same number.

app/api/vbc.py · summary()

Open tasks

Population panel, fourth card

Formula

count of work tasks with status open, any owner. Tenant-wide

Why this wayThe denominator the two task cards come from: Tasks due today and Overdue tasks are subsets of it (Part I, Prioritisation, opens with the same thirty-two). A started task keeps status open, so it stays in this count.

In the demo32 tasks

Check it yourselfMy Work: the Team button carries the same number.

app/api/vbc.py · summary()

My Work

34 values

Care managers open My Work to see their own open tasks, ranked by what is happening to each member and with the reason each task exists. VBC managers use the Team view of the same screen to find everyone's open work, including tasks nobody owns.

My Work N (count in the panel title)

Panel title, beside 'My Work' (or 'Team work' in the Team view)

Formula

number of rows in the table: the open tasks in the selected view (Mine = open tasks you own; Team = every open task in the tenant). When the queue was opened from an Exception Center row, only that rule's tasks are kept (filter on rule_key). 'Open' means status == 'open' exactly, so tasks someone has started are still counted.

Why this wayThe title counts what is on screen, while the Mine and Team buttons always count the whole tenant. So when the queue is filtered to one exception (an amber badge names the rule), the title can be smaller than the buttons beside it. That is intended: the filter is visible and 'all work' removes it. Neither view is narrowed by panel or attribution: Team is the whole tenant.

In the demo31 tasks

Check it yourselfcount the rows in the table; with no exception filter the title equals the Mine button in the Mine view and the Team button in the Team view

app/static/app.js · rWork()app/api/vbc.py · my_tasks()

Mine N

Scope toggle, top right

Formula

number of tasks in the tenant with status == 'open' and owner_id equal to the signed-in user

Why this wayThe count is the same whichever view is selected, because both counts come back with every response. That lets the toggle show what is on the other side without a second request. Started (in-progress) tasks are included, because a started task is still open.

In the demo31 tasks

Check it yourselfin the Mine view with no exception filter, the table has exactly this many rows

app/api/vbc.py · my_tasks()

Team N (also 'of N open task(s)' in the footer note and in the empty state)

Scope toggle; 'Where this work came from' note; the 'Your queue is clear' message

Formula

number of tasks in the tenant with status == 'open', whoever owns them and including tasks with no owner

Why this wayOne number, printed in three places on this screen. It is tenant-wide, not limited to the signed-in user's panel. The empty state compares it with Mine (team > mine) to tell a care manager with a clear queue that work is waiting in the Team view.

In the demo32 tasks

Check it yourselfswitch to Team: the table has this many rows

app/api/vbc.py · my_tasks()app/static/app.js · rWork()

N unassigned, and the UNASSIGNED row badge

Team button ('Team 32 · N unassigned'); Team-view note; empty state; Owner column badge

Formula

badge: the task has no owner_id. Count: number of open tasks in the tenant with no owner_id, from every source (rule, campaign, transitions sweep, typed by hand)

Why this wayA task is unassigned when it was typed in without an owner, or when a rule found nobody to give it to. Rules assign by continuity first (the member's current owner), then to the holder of the target role with the fewest open tasks, and leave the task unowned only when nobody holds that role (see 'Owner' below). The count and the badges describe the same set, so they agree whenever the table shows the whole Team view.

In the demo1 tasks

Check it yourselfswitch to Team and count the amber UNASSIGNED badges; they equal N

app/api/vbc.py · my_tasks()app/services/task_rules.py · _assign()

AUTO badge, and 'X of N open task(s) were raised by a rule from an event'

Member column badge; 'Where this work came from' note under the table

Formula

badge: source != 'manual'. X = number of open tasks in the tenant with source != 'manual'. The sources are rule, campaign and transitions (and manual).

Why this wayThe note exists to report how much of the queue came from events rather than from typing. It is honest about the ratio but broader than its wording: a campaign that a person launched also counts as 'raised by a rule from an event' and also gets the AUTO badge. X counts the whole tenant whichever view is shown.

In the demo26 tasks

Check it yourselfswitch to Team and count the blue AUTO badges; they equal X

app/api/vbc.py · my_tasks()app/static/app.js · rWork()

'Z were typed in by a person'

'Where this work came from' note under the table

Formula

number of open tasks in the tenant with source == 'manual'. Every task has a source (default manual), so X + Z = Team N always.

Why this wayReported, not asserted: the note sits beside the claim that work should come from events and rules, so a reader can see how far that is true of this queue.

In the demo6 tasks

Check it yourselfin the Team view, the rows reading 'created by hand' under the member name number Z

app/api/vbc.py · my_tasks()

IN PROGRESS badge, and 'N are in progress'

Reason column badge ('<name> started this'); 'Where this work came from' note

Formula

badge: started_at is set. N = number of open tasks in the tenant with started_at set. Derived, never stored as a status.

Why this wayIn progress is not a status. The task stays open, so it stays in the queue and in every open count, and it does not disappear from other screens that filter on open. Pressing Start sets started_at and also claims an unowned task, so two people do not work the same call.

In the demo0 tasks

Check it yourselfzero after a reset. Press Start on any row: the badge appears on that row and N goes up by one

app/api/vbc.py · my_tasks()app/static/app.js · taskStart()

Priority badge (Critical / High / Medium / Low)

Priority column, top of each row

Formula

the task's stored priority. Typed in by hand: the label chosen. Raised by a rule: the rule's own grade where it has one (abnormal lab: Critical at 30% or more outside the range, else High), otherwise the rule's configured priority. A few rules may later raise it one band (see 'Escalated priority').

Why this wayThe badge prints the word the record holds, coloured red, amber, blue and green. It is one input to the ranking, worth 20 / 12 / 6 / 2 points, and not the ranking itself (see Part I, Work-queue ranking). So a Medium task can sit above a Critical one.

In the demoHigh

Check it yourselfthe top row's badge. Hover 'score N' under it: the first line is '+points marked <label>'

app/static/app.js · priorityBadge()app/services/task_rules.py · evaluate()

Escalated priority, and '[escalated to vbc_manager]' in the reason

Priority badge and the grey reason line

Formula

for an open rule-raised task whose rule sets escalate_after_days and escalate_to_role (as shipped: gap.evidence_missing and appointment.not_rebooked, 7 days, vbc_manager): when today − (due date, else the business date it was created) >= 7, the priority moves up one band (Low→Medium→High→Critical; Critical stays Critical) and [escalated to vbc_manager] is added to the reason. It happens once per task, guarded by a task.escalated audit row.

Why this wayEscalation adds urgency without taking the task away from its owner: the owner is not changed. Both rules raise their tasks with no due date, so in practice the 7 days run from the day the task was created. The Exception Center's 'Escalated' card describes this as 'raised to a second role', but no reassignment happens; the role appears only as text in the reason.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone in a fresh tenant. It appears on a task-rule sweep once an 'evidence missing' or 'missed visit not rebooked' task is 7 days old

app/services/task_rules.py · _escalate_overdue()

score N

Priority column, under the badge

Formula

sum of the points of every component that fires: authored priority + due date + age + member risk + post-discharge window + authorization clock + worst open gap + stalled outreach (see Part I, Work-queue ranking). Maximum 20+55+21+22+28+30+18+14 = 208.

Why this wayComputed each time the screen loads and never stored, so the order moves as the calendar does, with no recompute job. Part I says the care manager 'sees the composed sentence, not the number'. In fact the screen shows both: the sentence in the Reason column and the number here. In a freshly reset tenant only five of the eight components can fire; age, the authorization clock and stalled outreach have nothing to act on.

In the demo107 points (top row)

Check it yourselfhover 'score N': the +points in the tooltip add up to N

app/services/ranking.py · rank()

Score tooltip ('+points why · +points why …')

Hover 'score N' in the Priority column

Formula

every component that fired, each written +points why, joined with · , in a fixed order: priority, due, age, member risk, post-discharge window, authorization, gap, engagement. The 'marked <label>' line is always first, even at +0.

Why this wayThe tooltip shows the evidence behind the order, so the queue explains its ranking rather than just asserting it. It is in engine order, not sorted by points. The worked example in Part I is sorted by points and shortens the gap line to 'HbA1c Control'. The screen shows the full measure name and adds ', N open in total' when the member has more than one open gap.

In the demo+12 marked High · +55 overdue by 60 days · +22 high-risk patient · +18 critical-priority gap: HbA1c Control — Glycemic Assessment, 5 open in total

Check it yourselfhover the top row's score and compare it word for word

app/static/app.js · rWork()app/services/ranking.py · rank()

'+N marked <label>' (authored-priority points)

Score tooltip, first line

Formula

Critical 20, High 12, Medium 6, Low 2, any other label 0

Why this wayThe human label counts, but it can be outscored by what is happening to the member (see Part I, Work-queue ranking). It used to be the heaviest component at 40.

In the demo12 points (top row)

app/services/ranking.py · rank()

'+N overdue by D days' / 'due today' / 'due in D days' (due-date points)

Score tooltip; also the first phrase of many Reason headlines

Formula

D = due date − today, where today is the US business date (America/New_York). D < 0: 35 + 2 × min(days late, 10), i.e. 37 to 55. D = 0: 25. D = 1–2: 18. D = 3–7: 10. D > 7: 3. No due date, or one that is not a date: no line.

Why this wayLateness counts most, and stops adding after 10 days so an ancient task cannot bury today's work. Part I prints the overdue range as '35–55'. The code never gives 35: one day late is already 37. The business-date clock stops the queue from calling today's tasks overdue after 20:00 Eastern, when UTC has already rolled over.

In the demo55 points (top row)

Check it yourselfa task more than 10 days late shows the +55 cap; one 1 day late shows +37, and one 4 days late shows +43

app/services/ranking.py · _due_component()

'+N open D days' (age points)

Score tooltip

Formula

D = whole 24-hour periods since the task was created (elapsed time, UTC). Under 3: no line. From 3: min(D, 21) points.

Why this wayAge breaks ties. It lets low-priority work with no due date eventually surface. Part I says 'after 3 days, one per day', which reads as one point on day 4. The code gives 3 points on day 3 and 21 from day 21.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfa reset creates every task at reset time, so no task is 3 days old yet. The line appears on any task still open 3 days later

app/services/ranking.py · _age_component()

'+N high-risk patient' (member risk points)

Score tooltip; 'high risk' / 'rising risk' in the Reason headline

Formula

the member's stored risk tier: HIGH 22, RISING 15, MEDIUM 6, LOW 0 (no line)

Why this wayIt applies to every task of the member, whatever the task is about. The tier itself comes from the clinical risk score (see Part I, Clinical risk score and tier). A task with no member gets only the priority, due and age components.

In the demo22 points (top row)

app/services/ranking.py · rank()

'+28 discharged N day(s) ago, 48-hour contact window open until DATE' / '+20 … contact window missed'

Score tooltip; 'post-discharge contact window open' in the Reason headline

Formula

the member's most recent inpatient discharge within 120 days. N = business today − discharge date, in days. N ≤ 2 (48 // 24): 28 points, open until discharge + 2 days. Otherwise N ≤ 7: 20 points, visit due by discharge + 7. Otherwise: no line.

Why this wayThe window closes whether or not anyone acts, so it ranks above almost everything while it is open (see Part I, Transition-of-care windows). It lifts EVERY open task of that member, not only the discharge tasks. The '48 hours' are counted as whole calendar days, so a member discharged just after midnight stays 'open' for almost 72 hours.

In the demo28 points (first row that has it)

Check it yourselfevery row for the member discharged two days before the reset carries +28, including a social-barrier task that has nothing to do with the discharge

app/services/ranking.py · rank()app/services/transitions.py · recent_discharges()

'+N <PA ref> decision due in Hh' / 'is past its decision deadline' / 'is on the SLA clock'

Score tooltip; 'authorization on the SLA clock' in the Reason headline

Formula

the member's undecided authorization with the soonest decision_due. H = hours until it (elapsed time, printed whole with the fraction dropped). Past it: 30. H < 24: 25 expedited / 20 standard. H < 72: 18 / 12. Otherwise: 5.

Why this wayThis is the regulatory clock the authored priority could never express (see Part I, Prior-authorization decision clock). Only the soonest authorization counts, and it lifts every task of that member, not only the task about the authorization.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone of the demo queue's members has an undecided authorization with a deadline, so no line shows after a reset. Submit an authorization for a member with a task, and every task of that member gains the line

app/services/ranking.py · _sla_component()

'+N <priority>-priority gap: <measure>, K open in total' (worst open gap points)

Score tooltip; the measure name in the Reason headline

Formula

among the member's open gaps (OPEN, IN_PROGRESS, SCHEDULED, AWAITING_AUTH, SERVICE_COMPLETED, REOPENED), the one with the highest priority: Critical 18, High 12, Medium 6, Low 2. ', K open in total' is added when K > 1.

Why this wayIt uses the worst gap's priority, not the number of gaps, because three Low gaps are not one Critical gap. A gap the member has attended but that has no evidence yet (SERVICE_COMPLETED) still counts as open here.

In the demo18 points (top row)

app/services/ranking.py · rank()

'+14 <intervention> sits at <status> — reached out, nothing back'

Score tooltip; 'outreach stalled' in the Reason headline

Formula

14 points if ANY intervention of the member is at OUTREACH, UNREACHABLE or DECLINED. There is no time test.

Why this wayIt flags members whose outreach needs a different channel. Part I calls it 'sent, nothing back'. The code has no waiting period: outreach started today already scores 14, and DECLINED is an answer, not silence. It is a different quantity from the rule 'Outreach with no movement' (engagement.stalled), which raises a task only after 21 idle days.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfno queue member has outreach at those statuses after a reset. Press 'Begin outreach' on a member's intervention, and every task of that member gains +14 at once

app/services/ranking.py · rank()

Row order

The table, top to bottom

Formula

score, highest first; ties broken by due date, earliest first (no or unreadable due date last), then by creation time, oldest first

Why this wayThe tie-breaks make the order stable across reloads instead of whatever the database returned (see Part I, Work-queue ranking). The order is the only output; no rank number is shown. It applies to the rows in view, Mine or Team, after any exception filter.

In the demo107, 107, 101, 94, 94 (scores of the first five rows)

Check it yourselfthe first two rows tie on score; the one due earlier is above the other

app/services/ranking.py · rank()

Reason headline (bold), e.g. 'overdue by 60 days + high risk + HbA1c Control — Glycemic Assessment'

Reason column, bold line

Formula

sort the components by points, highest first (ties keep engine order); take the first three; drop any worth 0; join their short phrases with + . Phrases: the due/age text as-is, 'marked <label>', '<tier> risk', the gap's measure name, 'post-discharge contact window open' / 'overdue', 'authorization on the SLA clock', 'outreach stalled'.

Why this wayThe headline says what is true about the member in a few words, not what somebody typed. Three phrases fit a table cell; the tooltip keeps the full list. The grey line under it is the rule's own reason text (see the rows below).

In the demooverdue by 60 days + high risk + HbA1c Control — Glycemic Assessment

Check it yourselfthe three phrases are the three largest +points in that row's tooltip

app/services/ranking.py · priority_reason()

Reason headline fallback (single top factor)

Reason column, bold line, only when the composed headline is empty

Formula

the full 'why' sentence of the single highest-point component (the first one on a tie)

Why this wayKept as a safety net, and effectively unreachable. The authored-priority component always exists and is worth at least 2 for every standard label, so the headline is empty only for a non-standard label. A second helper that would show this value with a tooltip (rankCell) is defined in the page code but never called.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnever shown in the demo: every row has a composed headline

app/services/ranking.py · top_reason()app/static/app.js · rWork()

OVERDUE badge

Due column, under the date

Formula

the first 10 characters of due sort before today's date, where today is the US business date (America/New_York). A task due today is not overdue. A due that is not a date never shows the badge.

Why this wayIt is the same boundary the ranking uses for its overdue points, so the badge and the '+N overdue by D days' line always agree.

In the demo8 rows badged in the Mine view

Check it yourselfcount the red OVERDUE badges; every one of those rows has an 'overdue by' line in its tooltip, and no other row does

app/api/vbc.py · my_tasks()

Origin line under the member name (rule title / 'campaign' / 'transitions sweep' / 'created by hand')

Member column, grey line

Formula

the title of the rule that raised the task, looked up from the rule catalogue by rule_key; with no rule: 'campaign' if source == 'campaign', 'transitions sweep' if source == 'transitions', else 'created by hand'

Why this wayIt names the rule in a care manager's words ('Post-discharge contact window'), not the mechanism, so the person can see why a task they did not create is in their queue.

In the demoHRA past its contractual window

app/static/app.js · rWork()app/api/vbc.py · my_tasks()

Due date of a rule-raised task

Due column

Formula

set once, when the rule raises the task. Post-discharge: discharge + 2 days for contact (ceil(48h ÷ 24)), + 3 for medication reconciliation and barriers, + 7 for the visit and the assessment. Care gap due soon: the gap's date; today if the gap says 'now', 'overdue' or 'asap'; otherwise today + 14. Abnormal lab: result date + 7. Authorization at risk: the date of decision_due. Appealable denial: decision date + 60. Coverage ending: the coverage end date. SLA breach and HRA: the missed date. Social barrier, enrolment and engagement tasks: the day raised. Evidence missing and missed visit not rebooked: no date.

Why this wayDischarge windows count from the discharge date, not from the day the record arrived (see Part I, Transition-of-care windows). A later sweep never re-dates an existing task: one task per rule and trigger. Two known limits. The authorization date is the UTC calendar date, so a late-evening Eastern deadline can show the next day. The task rule rounds the contact hours up, while the Transitions screen rounds its fixed 48 hours down; the two agree at 48 hours but would split if a tenant tuned the rule.

In the demo60 days before the reset

Check it yourselfthe 'Discharged … — follow-up visit due within 7 days' row is due 7 days after the discharge date in its reason

app/services/task_rules.py · _r_transition_contact()app/services/task_rules.py · _transition_step()app/services/task_rules.py · _r_gap_due_soon()app/services/task_rules.py · _r_pa_sla_at_risk()

'N% outside the reference range' (abnormal-result tasks)

Reason column, grey line of an 'Abnormal result with no follow-up' task

Formula

above the range: 100 × (value − upper) ÷ |upper|; below: 100 × (lower − value) ÷ |lower|; rounded to a whole percent. Only the latest abnormal result per member per test in the last 30 days. Under 10%: no task. 30% or more: Critical, else High.

Why this wayThe lab's abnormal flag has no size, so the rule measures how far outside the range the result is before it interrupts anyone. A result with no reference range is not treated as normal. It still raises a High task, which says the platform could not grade it.

In the demo14 % (highest-ranked lab task)

Check it yourselfevery graded lab task in the demo is below 30% and shows High; the HbA1c with no range says it could not be graded

app/services/task_rules.py · _deviation_pct()app/services/task_rules.py · _r_lab_critical()

'<PA ref> (urgency) decision due in Hh' / 'decision overdue' (authorization-at-risk tasks)

Reason column, grey line

Formula

for an authorization at SUBMITTED, IN_REVIEW or PENDED: H = hours until decision_due, with the fraction dropped (23.9 prints '23h'). The task is raised when H ≤ 24, and says 'overdue' once H < 0.

Why this wayIt warns the UM nurse a day before the regulatory deadline (see Part I, Prior-authorization decision clock). The hours are frozen into the reason when the task is raised; the score tooltip's authorization line is recomputed on every load.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone after a reset. Raised by a task-rule sweep within 24 hours of a pending authorization's deadline

app/services/task_rules.py · _r_pa_sla_at_risk()

'appeal window closes DATE' (denied-authorization tasks)

Reason column, grey line; the same date is the Due date

Formula

for a DENIED authorization: raised while today − decision date ≤ 60; closes on decision date + 60 (the rule's appeal_days)

Why this wayIt keeps a denial in front of a care manager while an appeal is still possible. Known conflict: the rule uses a flat 60 days for every line of business, while the appeals engine defaults to 180 days (60 for Medicare Advantage). For a non-MA denial, the task names a closing date 120 days before the appeals engine would close the window.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone after a reset. Deny an authorization, run the task rules, and the task appears with the closing date 60 days out

app/services/task_rules.py · _r_pa_denied_appealable()

'coverage ends DATE (N days)' (coverage-ending tasks)

Reason column, grey line

Formula

N = coverage end date − today, in days; raised when 0 ≤ N ≤ 30, and only for members enrolled in a programme

Why this wayA member in care management who loses coverage drops out of the programme; 30 days leaves time to confirm continuing coverage. Coverage with no end date never raises a task.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone after a reset

app/services/task_rules.py · _r_coverage_lapsing()

'<intervention> has sat in outreach for N days' (outreach-with-no-movement tasks)

Reason column, grey line

Formula

for an intervention at OUTREACH or IDENTIFIED: N = today − date of its last recorded event; raised when N ≥ 21; due the day raised

Why this wayThe last event is the last deliberate act on the intervention. Not the same as the '+14 outreach stalled' score line, which has no day count at all (see that row).

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone after a reset

app/services/task_rules.py · _r_engagement_stalled()

SLA-breach tasks: vendor, internal referral, provider response, social-need follow-up, HRA

Reason column, e.g. '<vendor> has not delivered <service> — SLA was DATE', 'HRA was due DATE and is due'

Formula

raised when today − the object's due date ≥ 0 (grace 0) and the object is not settled: vendor sla_due_on, referral due_on, provider opportunity sla_due_on (NEW or VIEWED only), social-need follow_up_on, HRA due_on. The task's due date is that same date.

Why this wayBecause the grace is 0, a breach task is raised ON the deadline day, not the day after. So a task titled 'Vendor has missed its SLA' can appear while its own row shows 'due today' and no OVERDUE badge, and the provider portal still calls the opportunity not overdue (it uses due < today). The HRA reason prints the assessment's status word, which reads 'and is due' for an HRA two months late.

In the demo3 tasks in the Team view

Check it yourselfthe vendor task in the demo says its SLA was the reset day, yet its row has no OVERDUE badge

app/services/task_rules.py · _r_vendor_sla_breach()app/services/task_rules.py · _r_referral_sla_breach()app/services/task_rules.py · _r_opportunity_unanswered()app/services/task_rules.py · _r_sdoh_follow_up()app/services/task_rules.py · _r_hra_overdue()

'no evidence has closed the gap in 7+ days' / 'visit … was never rebooked'

Reason column, grey line

Formula

evidence missing: the gap is SERVICE_COMPLETED, and today − the date it reached that status ≥ 7. Missed visit: the member's most recent appointment is NO_SHOW or CANCELLED, and today − its date ≥ 5. Both are raised with no due date.

Why this wayAttendance is not the outcome. A gap stuck at 'attended' stays open on the measure, and a missed visit nobody rebooks is a member falling out of care. With no due date these tasks can never show OVERDUE, and their 7-day escalation clock runs from creation (see 'Escalated priority').

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnone after a reset

app/services/task_rules.py · _r_gap_evidence_missing()app/services/task_rules.py · _r_appointment_not_rebooked()

'Meets care-management criteria — …' tasks (how many appear)

Reason column, grey line

Formula

each evaluation takes the members currently ELIGIBLE, sorts them by number of criteria met (most first), and proposes the first 5 (max_per_run). A member who already has an offer task still takes one of the 5 places.

Why this wayThe rule is rationed because care management has limited capacity, and a queue with every eligible member in it gets scrolled past. The cap is in effect 'the top 5 eligible members each evaluation', not '5 new offers each run'. Ties between members with the same number of criteria keep database order. The eligibility criteria are in Part I, Care-management eligibility.

In the demo1 offer tasks in the Team view

app/services/task_rules.py · _r_enrollment_offer()

Owner of a rule-raised task

Owner column

Formula

continuity first: the owner of the member's most recently created open, owned task. Otherwise, the holder of the rule's target role with the fewest open owned tasks (ties by user id). Otherwise, no owner (UNASSIGNED).

Why this wayA member with three care managers effectively has none, so new work joins whoever already has the member. Continuity overrides the rule's target role: an authorization task meant for a UM nurse can go to the member's care manager. Tasks raised by a provider's response (review, dispute, more information) are assigned without a caseload count, so unless continuity applies they go to the lowest-id holder of the role, not the least-loaded one.

In the demoJane Smith

Check it yourselfreassign from the Owner drop-down; the next rule-raised task for that member goes to the new owner

app/services/task_rules.py · _assign()app/services/task_rules.py · _caseload()

My Panel

22 values

The Care Manager's first screen: every member who currently needs attention, ranked by a points score that explains itself, with the next best action and what is already running. Below it, a follow-through panel counts what happened to the interventions the signed-in care manager owns.

My Panel (N) — members needing attention

Count badge beside the My Panel title

Formula

number of members whose panel priority score is > 0, counted BEFORE the top-25 cut. For a care manager: every member in the tenant. For a provider login: only the provider's attributed members (scope.visible_ids).

Why this wayCounted before the cut so the badge says how many members need attention, not how many rows fit on the screen: the table shows at most 25 of them. Despite the title, the list is NOT narrowed to the signed-in care manager. The signed-in user only sets a mine flag on each row, which the screen does not display. A care manager therefore sees the whole tenant's attention list; the Dashboard's Today's priorities shows the same computation with 8 rows and the same total ('of N needing attention').

In the demo12 members

Check it yourselfCount the rows: with fewer than 25 members needing attention the table has exactly this many rows. Open Dashboard: Today's priorities says 'of N needing attention' with the same N.

app/services/panel.py · panel()app/api/population.py · my_panel()app/core/scope.py · visible_ids()

N of them have no task

Under the My Panel title; the same number is repeated in the bold note under the table ('… N of them right now')

Formula

number of members with score > 0 that have no WorkTask with status open, whoever owns the task. Counted over all members needing attention, not only the 25 rows shown.

Why this wayThis is the reason the panel exists: the task queue can only rank work somebody created, so a member who needs attention but has no task is invisible there. Any open task counts, including one owned by another care manager, so the number answers 'has nobody raised anything', not 'have I raised anything'. When more than 25 members need attention, N can be larger than the NO TASK badges visible in the table.

In the demo2 members

Check it yourselfCount the amber NO TASK badges in the Member column; with 25 or fewer rows they equal N.

app/services/panel.py · panel()

NO TASK badge

Member column, under the member's risk tier

Formula

shown when the member has no WorkTask in status open (has_task is false). Owner and task subject are not considered.

Why this wayThe badge is a presence test, not an ownership test: a member whose only open task belongs to someone else shows no badge, and a member whose tasks are all closed shows the badge even when an intervention is running (Derrick Boateng in the demo: an intervention at AUTH_APPROVED, no open task).

In the demoJon Doe, Derrick Boateng

Check it yourselfOpen the member's 360 and look at open tasks: a badged member has none.

app/services/panel.py · panel()app/static/app.js · rPanel()

Risk tier (e.g. 'HIGH risk')

Member column, under the name

Formula

the member's stored Patient.risk_tier: HIGH when the risk score is >= 60, RISING >= 35, MEDIUM >= 15, else LOW. See Part I, Population health.

Why this wayThe panel reads the tier stored on the member; it does not recompute it. The stored tier is refreshed only when risk is recomputed, and a user with record-edit rights can also set it by hand: a member created by hand carries the model version 'manual entry', and an edit changes the tier without changing the version. Until the next recompute a hand-set tier can differ from the model's latest score, which is what the risk header on Member 360 shows. The tier is worth 30 / 20 / 8 / 0 points in the priority score.

In the demoHIGH (John Doe)

Check it yourselfOpen the member's 360: the risk header shows the model's tier, which matches unless the tier was set by hand since the last recompute.

app/services/risk.py · score_patient()app/services/risk.py · rescore()

Priority

Priority column, bold number

Formula

sum of points: risk tier HIGH 30 / RISING 20 / MEDIUM 8; 8 per open gap with no live intervention; 2 per gap already being worked; 25 while the 7-day post-discharge visit window is open, plus 15 more once the 48-hour contact window has passed (days 3–7); 10 if any active medication has PDC below 0.80; 12 if a prior authorization is SUBMITTED, IN_REVIEW or PENDED; 14 if an intervention sits at OUTREACH, UNREACHABLE or DECLINED; 18 if the member has an open gap and no active coverage today; 10 if the member's top recommendation is 'Offer care-management enrollment'. Members scoring 0 are not listed. Computed at request time, never stored. See Part I, My Panel.

Why this wayA points table rather than a model, so a care manager can argue with each weight. Three places where the code is narrower or wider than Part I's table reads: (1) the discharge 25 applies for the whole 7-day visit window, not only while the 48-hour contact window is open, so a member discharged 5 days ago scores 40 from discharge; (2) the 'high risk and in no programme' 10 is added only when enrollment is the member's TOP recommendation — in the demo Maria Gonzales is HIGH risk and not enrolled, but her top recommendation is a Critical gap, so she scores 70, not 80; (3) the adherence test reads a PDC stored as 0 as 100%, so a member recorded at 0% earns no adherence points. Adherence, authorization and stalled outreach each count once per member however many medications, authorizations or interventions qualify. This is a different scale from the risk score (Part I's worked example: John Doe 85 on risk, 80 here) and from the My Work task score.

In the demo80 points (John Doe)

Check it yourselfOpen 'all factors' on the row: the + numbers add up to the Priority.

app/services/panel.py · panel()app/services/transitions.py · recent_discharges()app/services/nba.py · recommend()

N factor(s)

Priority column, under the score

Formula

number of entries in the member's component list (client-side components.length). One per signal that fired, at most 10.

Why this wayIt counts signals, not problems: five unworked gaps are ONE factor worth 40 points. A high count means the attention comes from several independent directions; it says nothing about size.

In the demo3 factors (John Doe)

Check it yourselfOpen 'all factors': it lists exactly this many lines.

app/static/app.js · rPanel()app/services/panel.py · panel()

Why (the sentence)

Why column, first line

Formula

the why text of the single highest-points component. Ties keep a fixed order: risk, unworked gaps, gaps in flight, post-discharge, contact overdue, adherence, authorization, stalled outreach, no coverage, no programme.

Why this wayOne sentence, so the reason for the rank is readable at a glance; the rest is one click away. The sentence carries computed values: the unworked-gap sentence counts all unworked gaps but names only the first three; the adherence sentence names the lowest-PDC medication and truncates its percentage (int), where Member 360 rounds it, so the two can differ by one point; the authorization sentence names the first undecided SUBMITTED / IN_REVIEW / PENDED authorization in database order, not the one due soonest.

In the demo5 open gap(s) with nothing running: HbA1c Control — Glycemic Assessment, Diabetic Retinal Exam, Statin Adherence (PDC >= 80%)

Check it yourselfOpen 'all factors': the Why sentence is the first (largest) line.

app/services/panel.py · panel()

all factors: '+points factor — why'

Why column, expandable 'all factors' (shown when a member has more than one factor)

Formula

every component that fired, sorted by points, largest first, each printed as +points factor — why.

Why this wayThe score ships with its own explanation, so a member's position can be checked rather than trusted. The factor names are the internal keys (unworked_gaps, post_discharge, …), not translated labels.

In the demo+40 unworked_gaps, +30 risk, +10 adherence (John Doe)

Check it yourselfThe + numbers sum to the Priority in the same row.

app/services/panel.py · panel()app/static/app.js · rPanel()

Row order (the ranking)

Table rows, top to bottom

Formula

sort by Priority, highest first; ties by member name A–Z; show the first 25.

Why this wayA name tie-break keeps the order stable across reloads. The order moves by itself as clocks move (a discharge window opening, an authorization going pending) because nothing is stored. The Dashboard's Today's priorities is the first 8 rows of the same ranking, and a cohort's member list is ranked by the same engine restricted to the cohort, so the three cannot disagree about a member.

In the demoJohn Doe 80, Maria Gonzales 70, Linda Tran 62

Check it yourselfOpen Dashboard: Today's priorities lists the same members in the same order.

app/services/panel.py · panel()

Next best action (e.g. 'Start intervention')

Next best action column, bold line ('nothing recommended' when empty)

Formula

the member's first recommendation in the engine's order (band Critical > High > Medium, then higher score, then name), from one tenant-wide pass capped at 500 recommendations. Labels: 'Start intervention', 'Track authorization <ref>', 'Try an alternate channel', 'Offer care-management enrollment'. See Part I, Next best action.

Why this wayOne pass for the whole tenant keeps the screen to one read of each table. Deferred or dismissed recommendations are left out. 'Nothing recommended' means every gap is already in hand (for example an intervention running) and the member is not a high-risk unenrolled member. The band that decided the order is not shown on this row.

In the demoStart intervention (John Doe)

Check it yourselfOpen the member's 360: its next-best-action list (same engine, same order) starts with the same action.

app/services/nba.py · recommend()app/services/panel.py · panel()

Reason under the action

Next best action column, grey line

Formula

the first 3 reason texts of that recommendation joined with ; . For an enrollment offer, the fixed sentence 'high-risk member is not enrolled in any care-management program'.

Why this wayThree reasons keep the row short; the full reasoning is on the member's 360. The Dashboard shows the same text cut to 90 characters.

In the demoOpen quality gap — HbA1c Control; Overdue 97 days; Critical priority on the measure package

Check it yourselfOpen the member's 360: the first recommendation's reasons begin with the same three.

app/services/nba.py · recommend()

Status: interventions in flight ('nothing running' when none)

Status column, one status badge per intervention

Formula

every intervention on the member, whoever owns it, whose status is not COMPLETED, GAP_CLOSED or CANCELLED.

Why this way'In flight' is defined by exclusion, so IDENTIFIED, UNREACHABLE and DECLINED interventions still appear here. The same definition decides whether a gap counts as 'already being worked' (2 points) or 'unworked' (8 points). The Cost Concentration screen's '(N unworked)' uses a different rule: there a COMPLETED intervention still counts as working the gap, so a gap whose intervention completed without closing it is unworked here and worked there.

In the demoDiabetic retinal exam AUTH_APPROVED (Derrick Boateng)

Check it yourselfOpen the member's 360, Care tab: the same interventions are listed as open.

app/services/panel.py · panel()

Status: 'auth <ref> · <status>'

Status column, grey line

Formula

the first prior authorization on the member with no decision (decided_at empty) and status SUBMITTED, IN_REVIEW or PENDED.

Why this wayShown because a pending authorization blocks scheduling. 'First' is database order (the query has no sort), not the one due soonest. The Start button uses a wider idea of 'pending': any undecided authorization linked to the gap's intervention, including a DRAFT. So a DRAFT authorization hides Start with no auth line shown, and a pending authorization not linked to the gap shows here while Start is still offered. See Part I, Prior-authorization decision clock.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfSubmit a prior authorization for a member on the panel; after a reload the line appears and Priority rises by 12.

app/services/panel.py · panel()

Status: 'appt <date>'

Status column, grey line

Formula

the earliest appointment with status BOOKED or CONFIRMED whose business-time date is today or later; the screen prints the first 10 characters of its UTC timestamp.

Why this wayOnly live bookings count, so a cancelled or no-show appointment never reads as care arranged. The printed date and the filter use different clocks: the filter uses US business time, the printed date is UTC, so an appointment in the US evening (after midnight UTC) prints the next day's date.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfBook an appointment for a panel member from the 360 Care tab; after a reload its date appears here.

app/services/panel.py · _next_appt()app/static/app.js · rPanel()

Status: 'N closed'

Status column, last line

Formula

the member's care gaps whose current status is CLOSED, all time, with no date window.

Why this wayIt tells a care manager what has already been achieved for this member. Gaps retired as not applicable or excluded are not counted, and a closed gap that was later reopened drops out. It counts the member's gaps, whoever closed them; it is not the same number as the follow-through 'Gaps closed' below, which counts the signed-in user's interventions.

In the demo0 closed (John Doe)

Check it yourselfOpen the member's 360, Care tab: count the gaps shown as closed.

app/services/panel.py · panel()

Status: 'last contact <date>' / 'never contacted'

Status column, last line

Formula

the business-time date of the member's most recent engagement record, of any channel, direction or delivery status; 'never contacted' when the member has none.

Why this wayAny engagement counts, so an inbound message or a failed send updates it. 'Never contacted' means no engagement is recorded in the platform, not that nobody tried by another route.

In the demonever contacted

Check it yourselfRecord an engagement (a call or a message) for the member; after a reload this line shows today's date.

app/services/panel.py · _last_contact()

Start button (shown or not)

Last column, beside Open 360

Formula

shown when all three hold: the signed-in user may write (vbc.write); the recommendation is actionable (no undecided authorization linked to the gap's intervention, of any status including DRAFT); and the recommendation is for a gap.

Why this wayThe screen only offers what the server would accept. An enrollment offer is actionable but is not about a gap, so it never gets a Start button. Starting still runs the authorization-requirement check on the server first.

In the demo10 rows show Start (care manager)

Check it yourselfRows showing 'nothing recommended' or an enrollment offer have no Start button.

app/static/app.js · rPanel()app/services/nba.py · recommend()

Interventions started

Did what I started land? — first card

Formula

count of interventions owned by the signed-in user, in any status (including CANCELLED), all time.

Why this wayThe denominator of the follow-through funnel. It is ownership, not authorship: a reassigned intervention moves to its new owner's count. There is no date window; the function accepts a 14-day parameter that is not used.

In the demo0 interventions

Check it yourselfClick Start on a panel row; after a reload this card goes up by one.

app/services/panel.py · follow_through()

Member reached

Did what I started land? — second card

Formula

owned interventions whose status is NOT IDENTIFIED, ASSIGNED, OUTREACH, UNREACHABLE, DECLINED or CANCELLED, all time.

Why this way'Reached' means the member responded, so an intervention that only sent a message does not count. It is defined by exclusion: ENGAGED and every later state (through COMPLETED and GAP_CLOSED) counts, so it always includes 'Service completed'. The Dashboard's engagement funnel 'Reached' counts from a list of states instead, and the two disagree on one: a REOPENED intervention counts as reached here and not on the Dashboard.

In the demo0 interventions

Check it yourselfMove one of your interventions to ENGAGED; this card goes up by one.

app/services/panel.py · follow_through()

Service completed

Did what I started land? — third card

Formula

owned interventions whose status is COMPLETED or GAP_CLOSED, all time.

Why this wayThe service happened. Started ≥ Reached ≥ Completed always holds, so the three cards read as a funnel.

In the demo0 interventions

app/services/panel.py · follow_through()

Gaps closed

Did what I started land? — fourth card

Formula

owned interventions whose linked care gap's CURRENT status is CLOSED, all time.

Why this wayIt counts interventions, not distinct gaps. A gap closes on clinical evidence that can arrive for reasons unrelated to the outreach, which the caveat under the panel says in bold: read it as what happened to the work, not what the work caused. A gap that later reopened drops out. Different from the per-member 'N closed' in the table and from a quality measure's numerator.

In the demo0 interventions

app/services/panel.py · follow_through()

N stalled

Note under the follow-through cards (shown only when N > 0), followed by each intervention's name and status

Formula

owned interventions whose status is OUTREACH, UNREACHABLE or DECLINED, with no age threshold.

Why this wayThese need a different channel or a caregiver contact, and nothing else surfaces them. There is no waiting period, so an intervention moved to OUTREACH today already counts. DECLINED is included and is described as 'reached out, nothing back', although a member who declined did answer. The Dashboard's 'Outreach needing follow-up' uses the same three states for the whole tenant; this note counts only your own.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfIn the fresh demo tenant the care manager owns no interventions, so the note is absent. Start an intervention and move it to OUTREACH: the note appears with N = 1.

app/services/panel.py · follow_through()app/static/app.js · rPanel()

Worklists

17 values

The lists behind the Command Center cards (open care gaps, care plans, interventions in flight, referrals, vendors, vendor performance). A VBC manager, executive or administrator opens them by clicking a card; each is a list you can act on, not a report.

Open care gaps (list count)

Open care gaps worklist, header count

Formula

count of care-gap rows whose status is not closed closed = CLOSED, EXCLUDED, NOT_APPLICABLE, GAP_CLOSED, RESOLVED, DUPLICATE

Why this wayThis is the list the Command Center's 'Open care gaps' card opens, and it uses the same rule (gap_is_open), so the card and the list agree. A gap being worked (in progress, scheduled, awaiting authorization) is still open: a gap closes on evidence, not on effort. The list shows at most 200 rows; the count is the full total.

In the demo29 open gaps

Check it yourselfOpen the Command Center, note 'Open care gaps', click it: the header count here is the same number.

app/services/worklists.py · care_gaps()app/services/workflow.py · gap_is_open()

Priority (per gap)

Open care gaps worklist, Priority column

Formula

looked up from the member's risk tier and the measure's domain: clinical/chronic measure: HIGH → Critical · RISING → High · MEDIUM → Medium · LOW → Medium any other measure: HIGH → High · RISING → Medium · MEDIUM → Medium · LOW → Low a member with no tier counts as MEDIUM

Why this wayThe same missed measure matters more on a high-risk member with a chronic condition, so priority is a lookup rather than a score: two inputs, sixteen fixed answers, nothing hidden. It is re-derived every time the measure is evaluated, so it follows the member's tier. The list is sorted Critical first, then by member name. This priority feeds both ranking engines (Part I, Work-queue ranking and Next best action).

In the demoCritical 6, High 10, Medium 12, Low 1

Check it yourselfOpen a Critical row's member: their risk tier is HIGH and the measure is a clinical one.

app/services/quality.py · gap_priority()app/services/worklists.py · care_gaps()

Why open

Open care gaps worklist, last column

Formula

the measure engine's own reason the member is not in the numerator, stored when the gap is created and refreshed on every evaluation: 'no qualifying result in the last N days' · 'latest <test> <value> on <date> is above the <threshold> threshold' · '<drug class> adherence NN%, below the 80% threshold' · 'no qualifying procedure in the last N days'

Why this wayThe sentence is not written for display; it is the evidence the numerator test produced, so it cannot say something the engine did not decide. See Part I, One member, one measure.

In the demolatest Systolic blood pressure 148.0mmHg on 2026-08-25 is above the 140 threshold

Check it yourselfOpen the member and the gap: the lab or procedure history shows exactly what the sentence says.

app/services/quality.py · meets_numerator()app/services/quality.py · evaluate_patient()

Active care plans (list count)

Active care plans worklist, header count

Formula

count of members with an ACTIVE care plan

Why this wayOne row per active plan. Rows are sorted with overdue reviews first, then by member name, so the plans that need a person come to the top.

In the demo1 plans

app/services/worklists.py · care_plans()

Goals and Met (per plan)

Active care plans worklist, Goals and Met columns

Formula

Goals = number of goals on the member's active plan Met = goals whose DERIVED status is MET: a goal linked to a care gap is met when that gap is CLOSED or RESOLVED a goal linked to a vendor referral is met when the referral is CONFIRMED a goal with no linked work is met only when a person marked it met

Why this wayA goal's status is derived from the work beneath it, so nobody can type 'met' over an open gap; only a goal with nothing linked relies on a person's word. The cost: a goal whose gap closed for an unrelated reason also reads as met.

In the demoJohn Doe: 8 goals, 0 met

Check it yourselfOpen the member's Care plan tab: each goal's status and the gap or referral it is linked to are shown side by side.

app/services/worklists.py · care_plans()app/services/careplan.py · _goal_status()

Next review / OVERDUE

Active care plans worklist, Next review column

Formula

OVERDUE when the plan's next review date is before today (today = the US business date, America/New_York)

Why this wayA review date in the past is a commitment already missed, so it is flagged rather than merely shown. Dates are judged on the US business calendar, not the server's clock, so the flag does not flip at the wrong midnight.

In the demo0 overdue reviews

app/services/worklists.py · care_plans()app/core/clock.py · business_today()

Interventions in flight (list count)

Interventions in flight worklist, header count

Formula

count of interventions whose status is not CANCELLED, COMPLETED, GAP_CLOSED, DECLINED or UNREACHABLE

Why this wayIn flight means somebody still has something to do. This is the same rule as the Command Center's 'Active interventions' card, which opens this list, so they agree. The care manager's Dashboard card of the same name excludes only GAP_CLOSED, CANCELLED and COMPLETED, so a declined or unreachable intervention counts there and not here.

In the demo1 interventions

app/services/worklists.py · interventions()

Stage and row order

Interventions in flight worklist, Stage column

Formula

Stage = the intervention's current state in its state machine (no arithmetic) row order: IDENTIFIED, OUTREACH, ENGAGED, SCHEDULED, then every other state, then member name

Why this wayEarliest stage first, because an intervention nobody has contacted yet is the one most likely to stall.

In the demoAUTH_APPROVED 1

app/services/worklists.py · interventions()

Last movement

Interventions in flight worklist, last column

Formula

the detail of the most recently APPENDED entry in the intervention's event trail; '—' when there is none

Why this wayIt reads the trail in the order entries were written, which is the order things happened, rather than re-sorting by timestamp.

In the demoauthorization approved; ready to schedule

app/services/worklists.py · interventions()

Referrals (list count)

Referrals worklist, header count

Formula

social (SDOH) referrals + vendor referrals + internal team referrals, one row each

Why this wayAll three kinds of referral in one list, because to the member they are all 'somebody was asked to help'. Sorted overdue first, then by due date.

In the demo3 referrals

app/services/worklists.py · referrals()

Due / OVERDUE (per referral)

Referrals worklist, Due column

Formula

social referral: due = created + 14 days (follow-up) vendor referral: due = referred + the vendor's contracted SLA days (7 when none) internal referral: due = created + the receiving queue's SLA (3 days unless the tenant sets one) OVERDUE = due before today AND the referral has not settled

Why this wayEach kind keeps its own clock because each is a different promise: a follow-up call, a contract, an internal service level. A referral that has settled is never overdue, however late it settled. See Part I, Referral SLA.

In the demo0 overdue now

Check it yourselfThe header's 'N overdue' chip is the count of red OVERDUE badges in the list.

app/services/worklists.py · referrals()app/services/referrals.py · sla_days()

Closed loop %

Referrals worklist, header badge

Formula

round( 100 × closed ÷ settled ) settled = referrals in a final state (any kind) closed = social RESOLVED + vendor CONFIRMED + internal OUTCOME_CONFIRMED none settled → no percentage, with the reason

Why this wayThe denominator is settled referrals, not all referrals, so a referral still in progress neither helps nor hurts the rate. Closed means the outcome was confirmed by the plan, not claimed by the vendor. Green at 80% or above. For a login limited to a panel, the list, the overdue count and this percentage are all computed over the panel's referrals; settled is judged per referral kind.

In the demo100 %

Check it yourselfIf nothing has settled yet the badge reads 'closed loop: n/a' and says why.

app/services/worklists.py · referrals()

Contracted vendors, SLA, open referrals

Contracted vendors worklist

Formula

vendors = every vendor on file (active or not) SLA = the vendor's contracted days (stored, not computed) open referrals = that vendor's referrals not in CONFIRMED, DECLINED, FAILED, CANCELLED

Why this wayA vendor is a contracted party, so its SLA is a contract term, typed once and read everywhere. Open referrals is everything that has not reached a final state.

In the demo5 vendors; SLA days 3, 5, 14, 7, 5; 2 open referrals

app/services/worklists.py · vendors()app/services/vendors.py · ensure_vendors()

Referrals and Confirmed (per vendor)

Vendor performance worklist

Formula

Referrals = every referral ever sent to the vendor Confirmed = those the plan confirmed as delivered (CONFIRMED)

Why this wayConfirmed, not delivered: 'delivered' is the vendor's claim, 'confirmed' is the plan's evidence.

In the demoMedRide NEMT 0/1, Nourish Home Meals 1/1, HouseCall Health Assessments 0/1

app/services/vendors.py · performance()

Closed loop (per vendor)

Vendor performance worklist

Formula

round( 100 × CONFIRMED ÷ settled ), settled = CONFIRMED + DECLINED + FAILED + CANCELLED no settled referrals → 'n/a' with the reason

Why this waySame denominator rule as the referrals list: only finished referrals are judged.

In the demoNourish Home Meals 100%

app/services/vendors.py · performance()

Median days (per vendor)

Vendor performance worklist

Formula

the days from referral to DELIVERED for each referral that has both dates, sorted; the value at position n ÷ 2 (for an even count that is the upper of the two middle values, not their average)

Why this wayIt measures the vendor's own turnaround (to delivery), not the plan's confirmation lag. Taking a real observed value rather than averaging the two middle ones keeps it a whole number of days a vendor actually took; the cost is a slight upward lean for an even count.

In the demoNourish Home Meals 4 d

app/services/vendors.py · performance()

SLA breaches (per vendor) and row order

Vendor performance worklist, last column

Formula

referrals past their SLA due date that have NOT settled rows sorted by breaches (most first), then by referral volume

Why this wayIt counts breaches that are still live, the ones a person can still chase; a referral that was late but has since been confirmed drops out. It is therefore a current-risk count, not a historical breach rate.

In the demo0 live breaches, all vendors

app/services/vendors.py · performance()app/services/vendors.py · is_overdue()

Patients list

3 values

The member roster: every staff role with patient read access (Care Manager, VBC Manager, Executive, PA Coordinator, Auditor, Administrator) sees every member of the tenant here; a Provider sees the same screen as "My Panel" in the sidebar, narrowed to the members attributed to them. It is a lookup list, not a work queue: it counts and classifies, it does not rank.

Patients (N)

Count badge beside the Patients title

Formula

number of rows returned by GET /vbc/patients: every Patient record in the tenant, then narrowed by scope.filter_patient_rows (a no-op for staff roles, the attributed panel for a provider). Counted in the browser as rows.length; the search box hides rows but does not change N.

Why this wayEvery member record, not only attributed members and not de-duplicated: the demo's unmerged duplicate "Jon Doe" (same date of birth and MRN as John Doe, arriving from the EHR feed) is counted as a second member until someone merges it. No row is dropped for missing coverage or a closed attribution period. The Dashboard's "Patients" card counts the same records and agrees for every staff role; it agrees for a provider too: both the Dashboard card and this list count the provider's panel. Rows are listed in the order the database returns them. The query sets no order, so the list is not ranked by risk or by anything else.

In the demo13 members

Check it yourselfCount the table rows, or download the CSV with the search box empty: same number. As Care Manager, Dashboard > Population > "Patients" shows the same N.

app/api/vbc.py · list_patients()app/core/scope.py · filter_patient_rows()app/static/app.js · rPatients()

My Panel (N), provider sign-in

The same count badge, when a provider opens "My Panel" in the sidebar (the page header still reads "Patients")

Formula

members with a PatientAttribution row where provider_id = the signed-in provider, the attribution period is open, and ended_on is empty. A provider login with no provider binding, or no open period, sees an empty list (never the whole tenant).

Why this wayAttribution is by provider identity, not by the free-text PCP name, so the PCP column on this screen does not decide who is on the panel. In the demo every one of Dr. P. Osei's five members shows a different declared PCP (Dr. Sarah Smith, Dr. Robert Jones or Dr. Karen White); the demo seed moves these five attribution rows to Dr. Osei after they were created from the declared PCP. Those rows still read method declared_pcp and "Declared PCP on record: Dr. …", which names a different physician. That is a known inconsistency in the demo data. A member outside the panel is not merely hidden: opening them by link answers not-found rather than forbidden, so a provider cannot learn the member exists. Two things share the name here: the Care Manager's "My Panel" is a different screen (members ranked by a priority score, see the My Panel chapter) and counts something else.

In the demo5 (John Doe, Sarah Martinez, Linda Tran, Ahmed Ali, Rose Chen) members

Check it yourselfSign in as doc@demo.provider: the badge shows the number above and exactly those names. Sign in as the Care Manager: the same screen lists the whole tenant (see "Patients (N)").

app/core/scope.py · visible_ids()app/core/scope.py · _provider_panel()app/api/vbc.py · list_patients()

Risk tier badge (HIGH / RISING / MEDIUM / LOW)

Risk column, one badge per member (column label and visibility are configurable in Administration)

Formula

the stored Patient.risk_tier, printed as-is. When the risk model last scored the member: HIGH if score >= 60, RISING >= 35, MEDIUM >= 15, else LOW. See Part I, Population health. Colour: HIGH red, RISING amber, MEDIUM blue, LOW green.

Why this wayThe list reads the tier stored on the member; it does not rescore. The stored tier is refreshed in only two places: the nightly demo reset, and the recompute call POST /population/risk/recompute (VBC Manager permission). No screen offers that call. New labs, conditions or admissions do not rescore the member on arrival, so the badge can lag the clinical record. A user with record-edit rights can also overwrite the tier by hand. A hand edit does not change the model name stored on the member, so a hand-set tier still carries the model's name. A member no model has scored shows a badge too: the column defaults to LOW. In the demo, "Jon Doe" (the unmerged duplicate) shows a green LOW with no score and no factors behind it. His Member 360 says "No risk model has scored this member" and the list does not. The Dashboard's Risk distribution counts that unscored LOW as a Low member.

In the demoJohn Doe HIGH, Sarah Martinez MEDIUM, Maria Gonzales HIGH, Sam Okafor RISING, Linda Tran RISING, Ahmed Ali RISING, Rose Chen MEDIUM, Derrick Boateng MEDIUM, Elena Petrova MEDIUM, Robert Kim MEDIUM, Fatima Hassan RISING, Grace Nwosu LOW, Jon Doe LOW (never scored)

Check it yourselfOpen a member: the Member 360 risk header shows the score and the factor list, and the tier matches the badge (for a scored member the factors add up to a score inside the band). For Jon Doe the 360 says no model has scored him.

app/services/risk.py · score_patient()app/services/risk.py · rescore()app/api/records.py · update_patient()app/api/vbc.py · list_patients()app/static/app.js · statusBadge()

Patient 360: header, gaps, interventions and authorizations

14 values

The care manager's record of one member (VBC managers, executives, PA coordinators, providers for their own panel and auditors can open it too). This chapter covers the header, the Overview's service cases, partner activity and recent activity, the care gaps and interventions on the Care tab, the transition badge on the Utilization tab, and coverage and authorizations on the Coverage tab. Figures are shown as the care manager Jane Smith sees them.

Age ('John Doe | 65')

Header, after the member's name

Formula

whole years from the date of birth to today's business date (America/New_York): year difference, minus 1 if this year's birthday has not happened yet

Why this wayWorked out by the server each time the record is read, never stored, because a stored age is wrong on the member's next birthday. Known defect: when the date of birth is missing or unreadable, the age function returns -1 rather than no value, so the header prints '| -1' where it should leave the age out.

In the demo65 years

Check it yourselfThe date of birth is printed two lines below the name. Subtract it from today's date.

app/api/vbc.py · get_patient()app/services/quality.py · _age_on()

Sex, plan line and 'not recorded: …'

Header, identity lines

Formula

sex: stored value male / female / other (any case) shown as Male / Female / Other; anything else is not shown plan: payer + plan of the member's coverage whose status is active (the dates are not checked); none → 'no active coverage on file' not recorded: names each of sex, PCP and active coverage that is empty

Why this wayThe header names a missing fact instead of leaving a blank, because a blank looks like a display fault. The plan line reads the coverage status only, like the summary strip's Coverage card. The Coverage tab's 'Coverage' line uses a different rule (the coverage with the latest end date, whatever its status; see the row on that line below).

In the demoJohn Doe: Male | UnitedHealthcare MA Choice; not recorded: nothing, Jon Doe: (sex not shown) | no active coverage on file; not recorded: sex, active coverage

Check it yourselfOpen Jon Doe (the unmerged duplicate): no sex is shown, the plan line reads 'no active coverage on file' and the header lists both under 'not recorded'.

app/api/vbc.py · get_patient()

Header risk badge (HIGH / RISING / MEDIUM / LOW)

Header, after the name

Formula

the tier stored on the member (Patient.risk_tier), the same field as the Patients list badge. When the risk model sets it: HIGH ≥ 60 · RISING ≥ 35 · MEDIUM ≥ 15 · LOW

Why this waySee Part I, Clinical risk score and tier, and the Patients list row on the risk tier. Known inconsistency on this screen: the Primary risk drivers panel on the Overview prints the tier of the latest scoring run, not this stored field. Editing the tier by hand (Edit patient) changes this badge and not the panel, so the two can show different tiers for one member. They agree in the fresh demo tenant.

In the demoheader HIGH; risk drivers panel HIGH

Check it yourselfCompare the header badge with the badge on the Primary risk drivers panel on the Overview tab.

app/api/vbc.py · get_patient()app/services/member360.py · risk_drivers()app/api/records.py · update_patient()

Service cases: 'N open', and each case's Open / Closed label and colour

Overview tab, Service cases panel (shown only when the member has a case)

Formula

N = the member's mirrored service cases with no closed date each case: 'Open' when it has no closed date, else 'Closed' colour: grey when closed · amber when the status text contains 'progress', 'open' or 'new' · blue otherwise

Why this waySalesforce is the system of record for cases; this panel is a copy, so a care manager does not work the member while a complaint is open without knowing about it. Open or closed depends on the closed date Salesforce sent, not on its status text, so a case sent as status 'Closed' without a closed date reads 'Open'. The count is only as current as the last sync (next row).

In the demo1 open: Transportation complaint (In Progress)

Check it yourselfEach case card below the count says Open or Closed; count the Open ones.

app/api/population.py · member_cases()app/static/app.js · casesPanel()

Service cases: 'as of <date time>'

Overview tab, Service cases panel header

Formula

the latest sync time across the member's cases, printed as the first 16 characters of the stored timestamp (UTC)

Why this wayA copied status without the time of the last copy invites a stale answer to be read as current. Known limit: the time is UTC and carries no zone label, so after 8 pm Eastern (daylight time) it already shows the next day's date.

In the demomoves with the clock — read it on screen

Check it yourselfIn the demo it is the time of the nightly reset, in UTC.

app/api/population.py · member_cases()

Partner Activity: status badge and its colour

Overview tab, Partner Activity table, Status column

Formula

label: each referral state printed from one table (DELIVERED 'Vendor reports it completed', CONFIRMED 'Completion confirmed by the plan, with evidence', …) overdue: the SLA due date is before today and the referral is not settled (settled = CONFIRMED, DECLINED, FAILED, CANCELLED) SLA due date = referral date + the vendor's contracted SLA days (7 when the vendor has none) colour: red if overdue, FAILED or DECLINED · green if CONFIRMED · grey if otherwise settled (CANCELLED) · blue otherwise rows: newest last activity first, by full timestamp

Why this wayDelivered is the vendor's claim and confirmed is the plan's evidence, so only confirmed turns green. The SLA clock starts at the referral, not when the vendor acknowledges it, so a slow acknowledgement cannot move the deadline. Part I, Referral SLA covers team referrals; vendor referrals use the vendor's own contracted days instead. A vendor with an SLA of 0 days is treated as 7.

In the demoMedRide NEMT: Service scheduled (blue), Nourish Home Meals: Completion confirmed by the plan, with evidence (green), HouseCall Health Assessments: Vendor acknowledged (blue)

Check it yourselfClick 'Closed-loop detail': the Referrals tab's Vendor referrals table shows the same referrals, with the SLA date in red when overdue.

app/services/vendors.py · member_view()app/services/vendors.py · is_overdue()app/static/app.js · p360Tab()

Most recent activity (six entries)

Overview tab, Most recent activity panel

Formula

every dated event on the member across all areas (tasks, referrals, gaps, authorizations, encounters, outreach, documents, …), sorted by (date, kind) newest first; the first 6

Why this wayOne list across all areas, so the order of events is visible. Known limit: the date has no time of day. Events on the same day are ordered by the kind's name in reverse alphabetical order (tasks, then referrals, provider, pa, interventions, gaps, engagement, encounters, …), not by when they happened. On a busy day the six shown are not the six latest events: in the demo all six of John Doe's are tasks raised today, ahead of care gaps opened the same day.

In the demo2026-09-24 Task raised — Start Intervention, 2026-09-24 Task raised — Close care gap, 2026-09-24 Task raised — Review result with member, 2026-09-24 Task raised — Discuss food insecurity and refer to a community resource, 2026-09-24 Task raised — Complete the health risk assessment, 2026-09-24 Task raised — Assign a care manager and open a care plan

Check it yourself'Full timeline' opens the Timeline tab. Its first six entries are the same six, in the same order.

app/services/member360.py · timeline()app/static/app.js · p360Tab()

Care Gaps (N) and 'step N of 6'

Care tab, Care Gaps panel: title count, and under each gap's status

Formula

N = every care-gap record for the member, in any status (closed and excluded included) step = position of the gap's status in OPEN → IN_PROGRESS → AWAITING_AUTH → SCHEDULED → SERVICE_COMPLETED → CLOSED, of 6; not shown for a status outside that chain (REOPENED, EXCLUDED, …)

Why this wayThe step is read from the gap's current status. Two inconsistencies on this screen: (1) this count includes closed gaps, while the summary strip's 'Open gaps' counts open ones only, so the two differ once a gap closes. (2) Under 'step N of 6' is the eight-step orchestration bar 'X of Y steps complete' (see the Gap orchestration row in the Patient 360 summary-strip chapter), which includes eligibility and authorization. The same gap therefore shows two progress figures: Derrick Boateng's retinal-exam gap reads 'step 2 of 6' with '5 of 8 steps complete' beneath it, because his authorization was approved while the gap status stayed IN_PROGRESS.

In the demoJohn Doe: 5 gaps, all step 1 of 6, Derrick Boateng, Diabetic Retinal Exam: step 2 of 6; orchestration 5 of 8 steps complete

Check it yourselfOn the Care tab, compare the 'step N of 6' line under a gap's status with the bar directly beneath the row.

app/api/vbc.py · get_patient()app/static/app.js · p360Tab()

Interventions (N) and each intervention's recent trail

Care tab, Interventions panel

Formula

N = every intervention for the member, in any status (completed and cancelled included) each card: its status and the last 5 entries of its event trail, oldest of the five first

Why this wayThe panel is the member's whole history of interventions. The summary strip's 'Active interventions' counts only those still in progress, so the two numbers differ once one finishes or is cancelled. Only five events are shown to keep the card short; the full trail is on the Timeline tab.

In the demoJohn Doe: 0, Derrick Boateng: 1 (Diabetic retinal exam AUTH_APPROVED)

Check it yourselfJohn Doe has none until the demo starts one from a gap. After that, the count here and the strip's Active interventions both read 1.

app/api/vbc.py · get_patient()app/static/app.js · p360Tab()

Transition of care: badge ('4/7 completed'), its colour, and the closing note

Utilization tab, Transition of care panel header and footer

Formula

plan for the member's latest inpatient discharge on record, with no look-back limit badge = done / total actions, the same arithmetic as 'X of Y actions completed' on Transitions colour: red if readmitted · green if closed · blue while the 30-day readmission watch runs · amber otherwise note = the plan's close state detail (see the Transitions chapter, Readmission monitoring)

Why this wayThe member's own record shows how far the transition has got, not only a date. Three things to know. (1) This panel has no look-back limit, while Transitions lists discharges from the last 120 days and the Utilization panel directly below counts the last 180 days. So Grace Nwosu shows a plan for a March discharge while the panel below reports no inpatient admission in 180 days, and Robert Kim's plan (124 days ago) is here and not on Transitions. (2) Green means closed, not successful: a plan that closed with 6 of 7 steps missed is green. (3) Dismissed steps count as completed.

In the demoSam Okafor: discharged 2026-09-22, 4/7 completed (amber) — 3 step(s) still outstanding: Clinical assessment, Follow-up appointment, Visit completion, Grace Nwosu: discharged 2026-03-12, 1/7 completed (green) — closed with 6 missed step(s) on the record

Check it yourselfOpen Sam Okafor, Utilization tab: the badge matches the plan's steps counted by hand (the 30-day watch and the closing step are not counted). Open Grace Nwosu: a green badge above a plan with six missed steps.

app/api/population.py · member_transition()app/services/transitions.py · plan()app/static/app.js · p360Tab()

Coverage (N)

Coverage tab, Coverage panel title

Formula

every coverage record for the member, active and terminated; the green 'active' badge = status active

Why this wayOnly one coverage is active at a time: adding a plan terminates the previous one and sets its end date to today. Known defect: if the coverage request fails, the panel shows 'No coverage on file' and a count of 0 instead of an error. That is a false absence, and coverage is what decides whether a service can be booked.

In the demoUnitedHealthcare MA Choice (active, 2026-01-01 to 2026-12-31)

Check it yourselfCount the table's rows.

app/api/records.py · list_coverage()app/static/app.js · coveragePanelHtml()

Eligibility & authorizations: 'Coverage' line

Coverage tab, Eligibility & authorizations panel

Formula

the member's coverage with the latest end date, whatever its status, shown as payer · plan (status); none → 'none on file'

Why this wayThe status is printed next to the plan, so a terminated plan says so. It is chosen by a different rule from the header plan line and the summary strip, which use the coverage whose status is active. In the fresh tenant each member has one coverage and all three agree. They can name different plans after a coverage change: terminating a plan sets its end date to today, so a replacement entered with an end date already in the past loses to the terminated plan here.

In the demoUnitedHealthcare · MA Choice (active)

Check it yourselfCompare with the plan line in the header and the Coverage table directly above.

app/api/population.py · member_authorizations()

'Last verified with the payer' (or 'never')

Coverage tab, Eligibility & authorizations panel

Formula

the date of the member's most recent eligibility check (the first 10 characters of its UTC timestamp) and its transaction number; no check → 'never' plus the sentence explaining it

Why this wayVerification is something done on a date, not a property of the record: 'active' on file is not the payer saying so today. Known inconsistency: the Check eligibility step in each gap's orchestration chain (Care tab) picks the latest check with a tie-break on id and dates it in business time (America/New_York). This line takes the UTC date and has no tie-break. A check run after 8 pm Eastern (daylight time) is dated tomorrow here and today on the Care tab. Two checks in the same second can also be picked differently.

In the demothe reset day

Check it yourselfOpen Derrick Boateng (verified on the reset day). John Doe reads 'never' until someone clicks Check eligibility in the header.

app/api/population.py · member_authorizations()app/services/orchestration.py · gap_chain()

Authorizations (N), headline ('Retinal exam PA — Approved'), status colour, 'scheduling unlocked / held'

Coverage tab, Eligibility & authorizations panel: title count, green badge, table

Formula

N = every prior authorization for the member, any status, newest submitted first headline = service name from a fixed list (92250 Retinal exam · 83036 HbA1c · 99385 Annual wellness visit · 93000 ECG · 99213 Office visit; any other code printed as the code) + ' PA — ' + the status in title case scheduling unlocked when status is APPROVED; otherwise 'scheduling held' (a denial shows its reason) green badge by the title = the newest approved authorization colour: green APPROVED · red DENIED · amber PENDED, IN_REVIEW · blue otherwise

Why this wayThe member's own record answers whether a service can be booked; the server applies the same rule when booking (see Part I, The method: scheduling stays blocked while a linked authorization is pending). Limits: (1) only an approval unlocks scheduling, so a partial approval reads 'scheduling held'; (2) title case prints IN_REVIEW as 'In_Review' and PARTIALLY_APPROVED as 'Partially_Approved'; (3) the wellness code G0439 used by the demo's Annual Wellness Visit gaps is not on the name list, so it would read 'G0439 PA — …'; (4) the 'Decided' column shows the decision date, or when there is none, the decision-due timestamp unlabelled and unformatted.

In the demoPA-2026-40188: Retinal exam PA — Approved, scheduling unlocked

Check it yourselfOpen Derrick Boateng, Coverage tab: one authorization, approved, with the green headline badge. The Authorizations screen shows the same reference and status.

app/api/population.py · member_authorizations()app/api/population.py · _service_label()app/static/app.js · authorizationsPanel()

Patient 360: summary strip, risk drivers, clinical panels, what needs attention, timeline

23 values

The care manager's single view of one member. Every figure below is shown for the demonstration member John Doe, as the care manager Jane Smith sees him.

Attributed provider (and 'why?': method, basis, since)

Header, attribution line

Formula

the member's attribution row for the open period that contains today (not ended); method, provider, the recorded basis, and the period start no row → 'Not attributed this period'

Why this wayAccountability is a recorded assertion with its method, because the method is the argument when a payer disputes the denominator. The method says how the member came to this provider: 'declared_pcp' when their declared PCP is the provider, 'roster' when they were assigned to the provider's panel (the demo provider's five members), with the overridden PCP kept in the basis.

In the demoDr. P. Osei by roster since 2026-01-01 (basis: Assigned to Dr. P. Osei's panel (synthetic demo roster); declared PCP on record: Dr. Sarah Smith)

app/services/member360.py · summary()

Summary: Risk tier and 'N factor(s)'

Summary strip, card 1

Formula

the tier stored on the member by the last scoring run (HIGH ≥ 60 · RISING ≥ 35 · MEDIUM ≥ 15 · LOW); N = factors recorded by that run

Why this wayThe tier is the risk engine's; see Part I, Clinical risk score. It is kept current by the record, not by whoever remembers: any change to a member's conditions, results, encounters or medications — through ingestion, the API or a feed — re-scores that member when it is saved, and a score is refreshed once a day when time alone moves it (a lab ageing out of its window). A tier set by hand is left as the person set it.

In the demoHIGH, 10 factors

app/services/member360.py · summary()app/services/risk.py · rescore()

Summary: Open gaps ('N critical · N high' / 'N closed')

Summary strip, card 2

Formula

the member's care gaps whose status is not closed; hint: open gaps with priority Critical and High (or, when there are none, gaps closed)

Why this wayThe same open rule as every other gap count (gap_is_open), for one member.

In the demo5 open (3 critical, 2 high), 0 closed

app/services/member360.py · summary()app/services/workflow.py · gap_is_open()

Summary: Recent utilization, 'N in 180d', 'discharged Nd ago'

Summary strip, card 3

Formula

window = encounters whose discharge date (or admission date, if not discharged) is within the last 180 days big number = inpatient + emergency encounters in the window (added in the browser) 'N in 180d' = every encounter in the window · 'discharged Nd ago' = today − the latest inpatient discharge in the window

Why this waySix months is long enough to show a pattern and short enough to be about now. The big number counts only acute use (inpatient and ED), which is the utilisation a care manager acts on.

In the demo1 acute (0 inpatient + 1 ED) of 2 encounters in 180 days

app/services/member360.py · summary()app/static/app.js · summaryStrip()

Summary: Active interventions (and their states / 'N total')

Summary strip, card 4

Formula

interventions in IDENTIFIED, OUTREACH, ENGAGED, SCHEDULED, AWAITING_AUTH, AUTH_APPROVED or IN_PROGRESS; hint = those states, or the total when none is active

Why this wayActive means the intervention is still moving toward its outcome. The worklists and Command Center use a slightly different list of 'not finished' states, so their counts can differ for an intervention in an unusual state.

In the demo0 active of 0

app/services/member360.py · summary()

Summary: Upcoming actions, 'N overdue', 'next <date>' and the Next action line

Summary strip, card 5 and the next-action line

Formula

open tasks for the member; overdue = open tasks due before today; next = the open task with the earliest due date (its action and reason)

Why this wayThe member's own queue in one line, so the care manager sees what is due before scrolling.

In the demo8 open, 3 overdue; next: Close care gap

app/services/member360.py · summary()

Summary: Coverage (Active / None, payer, ends)

Summary strip, card 6

Formula

Active when the member has a coverage row with status active; payer, plan and end date from the first such row

Why this wayCoverage decides who pays and whether an authorization rule applies. Known inconsistency: this card reads the status alone, while My Panel also requires today to fall between the coverage's start and end dates.

In the demoUnitedHealthcare MA Choice, ends 2026-12-31

app/services/member360.py · summary()

What needs attention: total, 'N needing attention first', 'N already in flight'

What needs attention panel, header

Formula

items = one per open gap (per measure) + labs over target not covered by a gap + low adherence + social barriers + a recent discharge + authorizations + coverage first = items of high severity · in flight = items with work already under way (a live intervention, open transition tasks, a PA with a reviewer)

Why this wayOne list across every source, so nothing is only visible on a tab. 'In flight' stops a care manager from starting work that someone else has already started.

In the demo7 items, 4 first, 0 in flight

app/services/attention.py · attention()

What needs attention: severity (dot colour) and order

What needs attention panel, each line

Formula

care gap: high if past its due date or Critical priority · medium if High · else low lab over target: high if ≥ 10% above the target · else medium adherence: high if PDC < 60% · else medium social barrier: medium · discharge: high within 7 days, else medium (shown for 30 days) denied authorization (last 60 days): high · no active coverage: high · coverage ending within 60 days: medium order: high, medium, low, then title

Why this wayEvery severity is a written threshold, and each line shows the arithmetic that fired it. Targets are population defaults, not individualised clinical targets, and the panel says so.

In the demohigh: Creatinine above target, high: Diabetic Retinal Exam, high: HbA1c above target, high: Systolic BP above target, medium: Annual Wellness Visit, medium: Social barrier identified, medium: Statin Adherence (PDC >= 80%)

app/services/attention.py · attention()app/services/attention.py · _severely_over()

What needs attention: value and trend ('8.9%; improved from 9.4%')

What needs attention, lab and gap lines

Formula

latest reading of the tracked result within 400 days; trend word only when there is more than one reading and the value changed: improved when latest < first (all tracked results are lower-is-better), else worsened targets: HbA1c 8.0% · systolic 140 · total cholesterol 200 · creatinine 1.3

Why this wayA number without its direction invites the wrong conversation: 8.9% is above target but moving the right way.

In the demo8.9%; improved from 9.4%

app/services/attention.py · _value_phrase()app/services/attention.py · _latest_by_code()

What needs attention: 'Due <date> — overdue' and the PA note

What needs attention, care-gap lines

Formula

a gap's due date = the end of the current attribution period (else 31 December), set when the gap is created; overdue when before today PA note: the payer policy's answer for the gap's service code under the member's active plan ('required' / 'not required'; no note when unknown)

Why this wayThe due date is when the measure year closes, because that is when an open gap becomes a missed one. The PA note comes from the same policy lookup the authorization screens use, so the two cannot disagree.

In the demo1 overdue gap lines

app/services/quality.py · gap_due_on()app/services/workflow.py · pa_required()

What needs attention: 'PDC N% vs 80% threshold'

What needs attention, adherence line

Formula

each active medication with PDC below 80%, shown as round(PDC × 100)%; left out when a statin-adherence gap already covers that drug class

Why this wayPDC (proportion of days covered) is a stored pharmacy input, not computed by the platform; 80% is the standard adherence threshold.

In the demoAtorvastatin 40mg adherence 62%, below the 80% threshold

app/services/attention.py · _adherence()

What needs attention: discharge ('N day(s) ago'), authorizations, coverage ending

What needs attention, other lines

Formula

discharge: today − latest inpatient discharge, shown for 0–30 days authorizations: pending ones (submitted, in review, pended) collapsed to one line with the soonest decision due; each denial in the last 60 days ('N day(s) ago', appeal window open) coverage: 'ends <date> (N days)' when 0–60 days away

Why this wayEach is a clock the member is on. The discharge line uses a 30-day window, while the Transitions screen keeps a discharge actionable for 120 days.

In the demoRecently discharged from hospital: Discharged 2026-09-22 (2 day(s) ago) from Apex Demo Medical Center.

Check it yourselfShown for Sam Okafor, discharged two days before the reset.

app/services/attention.py · _transition()app/services/attention.py · _authorizations()app/services/attention.py · _coverage()

Risk panel: 'N-point score', tier, model and date

Primary risk drivers panel, header

Formula

score = Σ points of every rule that fires (conditions, the 3+ chronic bonus, the most severe band of each lab within 730 days, social Z-codes, ED and inpatient use in 365 days with caps, each medication below 80% PDC) tier: HIGH ≥ 60 · RISING ≥ 35 · MEDIUM ≥ 15 · LOW read from the latest stored scoring run (model apex-risk-rules v2)

Why this wayA transparent points model, not a statistical one: every point names the fact that earned it. See Part I, Clinical risk score, for every weight. The panel shows the latest stored scoring run, and that run always matches the member's current record: saving a new result re-scores the member, and the score history keeps every earlier run. For John Doe the 9.4% HbA1c band (18 points) gave way to the 8.9% band (10 points) when the September result arrived: 85 points.

In the demo85 points, HIGH, model apex-risk-rules v2

app/services/member360.py · risk_drivers()app/services/risk.py · score_patient()

Risk panel: each driver's 'N pts', 'X% of score' and impact label

Primary risk drivers panel, each row

Formula

share = round( 100 × driver points ÷ total points, 1 ) impact: High impact ≥ 10 points · Moderate impact ≥ 6 · else Supporting factor

Why this wayShare tells a care manager which fact is doing the work; the impact label is a reading aid over the same points.

In the demoChronic kidney disease stage 3 (N18.3): 12 pts, 14.1%, High impact, Diabetes with hyperglycemia (E11.65): 10 pts, 11.8%, High impact, HbA1c above 8% (was 8.9%): 10 pts, 11.8%, High impact, Creatinine above 1.5 (was 1.8mg/dL): 10 pts, 11.8%, High impact

app/services/member360.py · risk_drivers()

Risk panel: the action on each driver

Primary risk drivers panel, each row

Formula

matched by subject (HbA1c, blood pressure, cholesterol, creatinine, adherence, ED/inpatient use, social) to a 'What needs attention' line: its action, or its in-flight label; a standing condition gets a note instead of a button

Why this wayA diagnosis explains the tier but is not something to close, so it gets an explanation rather than a false call to action.

In the demo5 drivers with an action button

app/services/member360.py · _action_for_driver()

Change over time: readings, first, latest, change, direction, at target

Change over time panel

Formula

per tracked result (HbA1c, systolic BP, total cholesterol, creatinine), over every reading on file: change = round( latest − first, 2 ); improved when change < 0, unchanged at 0, worsened above (all lower-is-better); a single reading is not evaluable at target when latest ≤ target (8.0% · 140 · 200 · 1.3)

Why this wayAn observed change, not a claim about what caused it; the panel says so. No time window, so the whole record on file is compared.

In the demoHbA1c 9.4 → 8.9 (-0.5, improved, target 8.0), Systolic BP 162.0 → 148.0 (-14, improved, target 140.0), Creatinine 1.9 → 1.8 (-0.1, improved, target 1.3)

app/services/outcomes.py · patient_trajectory()

Clinical panels: lab 'abnormal' highlight, problem count, medications with PDC %, encounters count

Labs, Problems, Medications, Encounters panels

Formula

abnormal (red) = the flag the sending lab put on the result (HL7 OBX-8 H/L/A/AA/HH/LL, or the file's abnormal column); not recomputed problems = every condition on the record · encounters = every encounter on the record PDC % = round(PDC × 100), red below 80%

Why this wayThe abnormal flag is the laboratory's statement, kept as sent: re-deriving it from a reference range would put words in the lab's mouth. PDC is a pharmacy input the platform stores, not a figure it computes.

In the demo3 problems, 6 of 6 lab results flagged abnormal, 2 encounters, PDC Atorvastatin 40mg 62%

app/services/ingest.py · promote()app/static/app.js · panelMeds()

Gap orchestration: 'X of Y steps complete', PA REQUIRED / NO PA NEEDED, '<step> blocked'

Each care gap's orchestration chain

Formula

eight steps, each read from its own record: gap · eligibility · PA requirement · launch PA · obtain authorization · schedule · confirm service · close gap Y = steps that apply (steps not required or not applicable are left out); X = steps done; bar green ≥ 80%, amber ≥ 40% blocked badges = steps blocked, denied, stale or unknown

Why this wayA progress bar that can never reach the end is one nobody reads, so a service with no authorization requirement has 6 steps, not 8. Each step is derived, never ticked.

In the demoHbA1c Control — Glycemic Assessment: 2 of 6 steps complete, Diabetic Retinal Exam: 2 of 8 steps complete, PA required, Statin Adherence (PDC >= 80%): 2 of 6 steps complete

app/services/orchestration.py · gap_chain()

Gap orchestration: eligibility 'verified Nd ago' / stale

Orchestration chain, Check eligibility step

Formula

age = today − the date of the latest eligibility check done when age ≤ 30 days · stale when older · no check: open when coverage is on file, blocked when there is none

Why this wayAn eligibility answer goes stale; a 30-day-old check is not evidence of coverage today.

In the demoopen: coverage on file (UnitedHealthcare MA Choice) but never verified with the payer

app/services/orchestration.py · gap_chain()

Close gap: when evidence closes a gap

Orchestration chain, Close gap step; completion modal

Formula

a gap closes only when the evidence supplied is an encounter, lab or procedure AND (its service code equals the gap's service code OR it names the gap's measure)

Why this wayApproval of an authorization never closes a gap; only clinical evidence of the service does. The check is on the evidence supplied with the event.

In the demo0 gaps closed on evidence so far

app/services/workflow.py · evaluate_completion()

Timeline: total, per-kind and per-team chip counts, 'Showing the most recent N of M'

Timeline tab

Formula

every dated event across encounters, results, medications, procedures, conditions, gaps, interventions, referrals, appointments, provider opportunities, care plan, assessments, authorizations, messages, calls, ladders, tasks and documents chip counts are taken BEFORE a filter is applied (a zero chip stays visible, disabled) shows the most recent 120 (up to 400 on request); 'N of M' when there are more

Why this wayOne chronology across every system, so the member's story reads in order. Counting before filtering lets a reader see what a filter hides.

In the demo44 events; Clinical 11, Encounters 2, Care Gaps 6, Tasks 8, Referrals 12, Provider 1, Care Plan 1, Engagement 3

app/services/member360.py · timeline()

Timeline: entry colour (red / amber / green)

Timeline tab, each entry

Formula

red: abnormal result, inpatient or ED admission, denied authorization, failed or declined referral amber: discharge, a fill below 80% PDC green: gap closed, authorization approved, referral confirmed or resolved, appointment completed

Why this wayColour marks events that change what should happen next, so a long timeline can be skimmed.

In the demored 7, amber 1, green 1

app/services/member360.py · timeline()

Patient 360: care plan, HRA, SDOH, referrals, appointments and programmes

25 values

The care manager's working tabs on one member: care-management enrolment, the health risk assessment, the care plan, social barriers, appointments and documents (Care tab), team, social and vendor referrals (Referrals tab), and Partner Activity on the Overview. Every figure below is read as the care manager Jane Smith sees it, for John Doe unless another member is named.

Care management: status badge (ENROLLED, OFFERED, … / MEETS CRITERIA / NOT ELIGIBLE), the four stage dots and 'Previously declined'

Care tab, Care management panel, header and stage row

Formula

live = the member's first programme enrolment whose status is ELIGIBLE, OFFERED, ACCEPTED or ENROLLED badge = the live status; with nothing live: MEETS CRITERIA when at least one eligibility criterion is met today, else NOT ELIGIBLE a stage dot is ticked when it is at or before the live status in ELIGIBLE → OFFERED → ACCEPTED → ENROLLED 'Previously declined / disenrolled' = the member's DECLINED or DISENROLLED row for cm-complex, shown only when nothing is live

Why this wayThe badge is the recorded lifecycle; the criteria are re-evaluated on every view. The two can differ, and the panel shows both rather than choosing: an ENROLLED member who no longer meets any criterion keeps ENROLLED (Sarah Martinez shows ENROLLED above 'does not currently meet the criteria'). A member who declined is shown as declined and is not re-offered automatically; offering again is a person's decision. Two limits. The live row may belong to any programme, while the 'previously' line reads only cm-complex. And the Recommended action card uses a different rule (see the Next best action chapter): it offers enrolment to every HIGH-risk member not yet ENROLLED, so Maria Gonzales, already OFFERED, is still recommended an offer (ranked sixth, so it is below the three cards Patient 360 shows, but it is on the Population list), while rising-risk members who meet the criteria get no offer card.

In the demoENROLLED; stages reached: ELIGIBLE, OFFERED, ACCEPTED, ENROLLED

Check it yourselfOpen John Doe → Care. Sam Okafor shows MEETS CRITERIA with a 'Previously declined' line and an 'Offer again' button.

app/services/enrollment.py · member_view()app/static/app.js · enrollmentPanel()

Care management: 'Why this member qualifies' (criteria met and their evidence)

Care tab, Care management panel

Formula

the four criteria of Part I, evaluated at the moment the panel loads: HIGH tier · RISING tier with 3 or more open gaps · an inpatient or ED admission in the last 180 days · 2 or more unaddressed social barriers each line prints the evidence the test used; none met → 'does not currently meet the criteria'

Why this waySee Part I, Care-management eligibility. The criteria are computed on every view and never stored as a flag, so the reasons are current. The acute test counts encounters by admission date. The social test uses the same barrier classification as this tab's Social drivers of health panel, so a declined referral still counts as unaddressed. Part I's worked example is out of date: the product today finds six members on eight criteria. Maria Gonzales also meets the recent-acute test, and Robert Kim qualifies on it; neither line is in Part I's table.

In the demoHigh-risk member (risk tier HIGH), Admission or ED visit in the last 6 months (1 acute encounter(s))

Check it yourselfOpen John Doe → Care: two lines, 'High-risk member' and 'Admission or ED visit in the last 6 months'. His Utilization tab shows the admission.

app/services/enrollment.py · evaluate()

Care management: 'NOT ACTUALLY STARTED' and the care manager

Care tab, Care management panel

Formula

only for an ENROLLED member: started when a care manager owns the member (the active care plan's owner, else the owner of the newest open task) AND an active care plan exists otherwise the red badge shows and the missing items are listed; when started, the owner is shown as Care manager

Why this wayEnrolment can be recorded directly, for example from a payer file or a programme older than the platform. So the platform reports an enrolment with nothing behind it rather than refusing to record it. Known inconsistency: the same panel's note says 'ENROLLED is not settable on its own', and Part I says enrolment assigns a care manager and opens a care plan first. Both are true only for the Start programme button, not for a directly recorded ENROLLED. In the fresh demo tenant, 7 of the 8 ENROLLED members have no care plan and show this badge. John Doe is the exception.

In the demoNOT ACTUALLY STARTED: no active care plan

Check it yourselfOpen Sarah Martinez → Care: ENROLLED, NOT ACTUALLY STARTED, 'no active care plan'. John Doe shows Care manager Jane Smith and no red badge.

app/services/enrollment.py · delivery_state()app/static/app.js · enrollmentPanel()

Health risk assessment: status badge and PAST WINDOW

Care tab, Health risk assessment panel, header (and the red Due date on each row)

Formula

HRA rows for the member, latest due date first; live = the first one not settled (COMPLETED, REFUSED and UNREACHABLE are settled) badge = the live status; else COMPLETED if any HRA was completed; else NONE DUE PAST WINDOW, and a red Due date = due date before today (business date) and not settled

Why this wayThe window is the contract's, so an HRA nobody attempted shows as past its window as plainly as one the member refused. Two limits. A late completion is shown as COMPLETED, with nothing on this screen marking it late; the in-window test exists only in the HRA completion KPI, which no screen renders. And this panel does not know about an in-home HRA referred to a vendor. John Doe shows DUE and PAST WINDOW while his Referrals tab shows HouseCall Health Assessments working an annual in-home HRA for him; the two records are not linked.

In the demoDUE, PAST WINDOW

Check it yourselfOpen John Doe → Care: DUE and a red PAST WINDOW; the row's Due date is red.

app/services/assessments.py · member_view()app/static/app.js · hraPanel()

Health risk assessment: Window and Due dates

Care tab, Health risk assessment table

Formula

raised automatically for every ENROLLED member with no live HRA first HRA: window from the enrolment date; due = enrolment date + 90 days later HRAs: window from the last settled HRA (its completion date, else its due date); due = that date + 365 days

Why this wayThe clock runs from enrolment, not from when somebody created the record: a window that starts when the plan notices is not a window. A refused or unreachable HRA starts the next annual cycle from its due date, so the member is asked again next year rather than on the next sweep. The 90 and 365 days are constants in code, not tenant settings. The missing HRA rows are created when this panel is read (the read also writes).

In the demo60 days before the reset

Check it yourselfJohn Doe enrolled 150 days before the reset; his first HRA was due 90 days later, so it is 60 days past due.

app/services/assessments.py · ensure_due()

Care plan: goal count

Care tab, Care plan panel, title

Formula

goals on the member's single ACTIVE care plan, every status including MET; proposals are not counted; a removed goal is deleted and leaves the count no plan → 'No care plan yet' and 'N goal(s) can be proposed from the record'

Why this wayThe count of lines on the plan, not a measure of progress. The plan's own summary also computes met and PA-blocked counts, but this panel does not show them.

In the demo8 goals

Check it yourselfCount the rows of John Doe's care plan table.

app/services/careplan.py · read()

Care plan: 'review due' date and REVIEW OVERDUE

Care tab, Care plan panel, header

Formula

review due = the day the plan was opened + 90 days; 'Mark reviewed' resets it to that day + 90 REVIEW OVERDUE when the review date is before today (business date)

Why this wayA plan nobody revisits is a document, not a plan. The 90 days are a constant in code, not a tenant setting. 'Started' is the day the plan record was opened, not the enrolment date. John Doe's plan is opened by the demo reset, so it reads as started on the reset day although he enrolled five months earlier.

In the demo90 days after the reset

Check it yourselfClick Mark reviewed: the date moves to today + 90 days.

app/services/careplan.py · ensure_plan()app/services/careplan.py · review()app/services/careplan.py · read()

Care plan: goal Status and 'from the work' / 'set by hand'

Care tab, Care plan table, Status column

Formula

read from the linked work; the first rule that applies wins vendor referral: SENT→REFERRED · ACKNOWLEDGED, MEMBER_CONTACTED→ACTIVE · SCHEDULED · DELIVERED→AWAITING EVIDENCE · CONFIRMED→MET · DECLINED, FAILED→BLOCKED · CANCELLED→CLOSED care gap: closed as CLOSED/RESOLVED→MET, closed otherwise→CLOSED · AWAITING_AUTH→PA REQUIRED · SERVICE_COMPLETED→AWAITING EVIDENCE · SCHEDULED · intervention AUTH_APPROVED→PA APPROVED · OUTREACH, ENGAGED, IN_PROGRESS→ACTIVE · else OPEN no link: the status a person set (OPEN until marked met) = 'set by hand'; everything else = 'from the work'

Why this wayA typed status can read 'active' over a gap nobody has touched, so a linked goal takes its status from the work and cannot be edited. Hovering the badge shows the basis. MET for a vendor line means CONFIRMED, with evidence, not the vendor's own DELIVERED. Two known defects. (1) Accepting a vendor proposal from this screen loses its vendor link, because the add-goal request has no vendor field. The new line reads OPEN · set by hand, and the same proposal is offered again. The two vendor lines in the demo were seeded with the link and are not affected. (2) For a gap, the plan reads the first intervention it finds for that gap, in no set order. So a gap that has been worked twice can show the older intervention's state.

In the demoComplete retinal screening: OPEN (from the work), Improve diabetes control: OPEN (from the work), Improve blood pressure control: OPEN (from the work), Complete annual wellness visit: OPEN (from the work), Address food insecurity: OPEN (set by hand), Improve medication adherence: OPEN (from the work), Address ride to retinal exam: SCHEDULED (from the work), Address annual in-home health risk assessment: ACTIVE (from the work)

Check it yourselfHover a status: the tooltip names the gap, intervention, authorization or vendor referral it was read from.

app/services/careplan.py · _goal_status()app/services/careplan.py · _vendor_goal_status()

Care plan: 'now X ↓/↑ (date)' and 'at target' under a goal's target

Care tab, Care plan table, Target column

Formula

for a goal with a target code and value: every numeric reading of that code on file, with no time window; latest = the newest by date at target = latest ≤ target (lower is better) or latest ≥ target (higher is better) arrow: ↓ improving or ↑ worsening, comparing the latest with the EARLIEST reading; no arrow with one reading or no change

Why this wayA target is shown with the reading and its date, because a target met months ago on a value nobody has taken since is not met today. Three things to know. The test is inclusive while the target text prints a strict '<', so an HbA1c of exactly 8.0 shows 'at target' beside 'HbA1c <8%'. There is no lookback window, unlike What needs attention (400 days). The arrow is always drawn ↓ for improving. That is right for every target the product proposes today, all lower-is-better, but a hand-entered higher-is-better goal would show the wrong arrow. Change over time on the Overview (Part II, the Patient 360 summary-strip chapter) compares latest with first in the same way.

In the demoImprove diabetes control: now 8.9% ↓ (2 readings), Improve blood pressure control: now 148mmHg ↓ (2 readings)

Check it yourselfCompare with the HbA1c and systolic readings in the Clinical tab's Labs & vitals table.

app/services/careplan.py · _progress()app/static/app.js · planProgress()

Care plan: 'N goal(s) are owned outside this system' (and 'outside this system' under an owner)

Care tab, Care plan panel, note under the table

Formula

goals whose owner is a label and not a platform user: Pharmacist, Social Work, Dietitian, Behavioral Health, Community Health Worker, Primary Care, or Vendor a goal given neither an owner nor a label falls to the plan owner and is not counted

Why this wayThe platform cannot send a task to a pharmacist or a social worker it has no account for, so it says so instead of pretending to assign the work. Adherence proposals default to Pharmacist, social-barrier proposals to Social Work, vendor proposals to Vendor.

In the demo4 goals

Check it yourselfCount the rows whose Owner column says 'outside this system'.

app/services/careplan.py · read()app/services/careplan.py · add_goal()

Care plan: 'Proposed from the record N' and each proposal's target

Care tab, Care plan panel (or the empty plan's 'N goal(s) can be proposed')

Formula

one proposal per What needs attention line that is a care gap, low adherence or a social barrier, plus one per unsettled vendor referral; minus anything already on the active plan (matched by care gap, else vendor referral, else title) target: a tracked result '<name> <target><unit>' (HbA1c <8%, Systolic BP <140mmHg) · adherence 'PDC ≥ 80%' · other gaps 'completed by <gap due date>' · social 'Barrier addressed' · vendor 'appointment <when>' or 'delivery by <SLA date>'

Why this wayNothing is invented: each proposal names the fact behind it, and adding it to the plan is a person's decision. Limits. Social proposals come from the attention list, which lists every positive screening answer. It does not merge repeat screenings or check whether the barrier is already referred or resolved, and it treats 'false' and '0' as positive answers, where the social panel treats them as negative. A vendor proposal accepted here is proposed again (see goal Status).

In the demoComplete retinal screening → completed by the gap due date, Address transportation insecurity → Barrier addressed, Complete annual wellness visit → completed by the gap due date, Improve diabetes control → HbA1c <8%

Check it yourselfOpen Sarah Martinez → Care: no plan yet, and the proposals listed here, each with its reason.

app/services/careplan.py · propose()app/services/attention.py · _social()

Social drivers of health: barrier count and 'N unaddressed · N in progress · N resolved'

Care tab and Referrals tab (the same panel on both)

Formula

barriers = one per Z-code with a positive screening answer, the newest per code; an answer of no, none, negative, no risk, false, 0 or blank is negative and dropped each barrier: RESOLVED if its latest community referral is RESOLVED · IN PROGRESS if that referral is not settled, or a contracted vendor in the matching category (transport, meals) holds an unsettled referral · else UNADDRESSED, including a DECLINED or UNRESOLVED referral the three counts add up to the barrier count

Why this wayA member who declined help still has the barrier, so it returns to unaddressed. A barrier a vendor is already working is not reported as ignored. The enrolment criterion and the population views use the same classification. Known defect: negative answers are dropped BEFORE the newest-per-code step. So a later 'no' on re-screening does not clear an earlier 'yes', and the barrier stays counted; the enrolment criterion has the same order. The Clinical tab has a panel with the same title that counts something else (next value).

In the demo1 barrier(s): 1 unaddressed, 0 in progress, 0 resolved

Check it yourselfOpen John Doe → Care or Referrals: one barrier, food insecurity, not addressed.

app/services/sdoh.py · barriers()app/services/sdoh.py · barrier_state()

Clinical tab: 'Social drivers of health N' (screening answers)

Clinical tab, Social drivers of health table

Formula

every social-history observation on the member, positive or negative, every repeat screening, all dates

Why this wayThis is the raw record of screening answers, newest first. It has the same title as the Care tab panel but counts a different thing: a member screened twice, or with a 'no' answer, shows more here than there. Neither panel says what it is counting.

In the demo1 screening answers

Check it yourselfCount the rows of the table; compare with the barrier count on the Care tab.

app/api/population.py · clinical_record()app/static/app.js · panelSdoh()

Barrier badge: referral status / WITH A VENDOR / NOT ADDRESSED

Care and Referrals tabs, each barrier, top right

Formula

computed in the browser: if the barrier has a community referral, the latest one's status, whatever it is · else, if a vendor is working it, WITH A VENDOR · else NOT ADDRESSED

Why this wayIt shows who holds the barrier, so a care manager does not start a second referral for a ride already booked. Known inconsistency: the badge is computed from the referral, not from the server's barrier state that feeds the counts, and they disagree in two cases. A DECLINED or UNRESOLVED referral shows that status although the header counts it as unaddressed. A declined referral with a vendor now working the barrier shows DECLINED and hides the vendor box, although it is counted as in progress. So counting badges does not always give the header's counts.

In the demoFood insecurity: NOT ADDRESSED

Check it yourselfJohn Doe's food-insecurity barrier reads NOT ADDRESSED, and the header says 1 unaddressed.

app/static/app.js · sdohBarrier()app/services/sdoh.py · barriers()

Barrier chain: Finding → Intervention chosen → Referral made → Follow-up → Resolution, and 'Follow-up due'

Care and Referrals tabs, each barrier

Formula

stage reached: RECOMMENDED 1 · SENT 2 · ACCEPTED, DECLINED, UNREACHABLE 3 · RESOLVED, UNRESOLVED 4 · no referral 0 Finding always done · Intervention chosen done at 1+ · Referral made done at 2+ · Follow-up overdue when the follow-up date is before today and the referral is not settled, else done at 3+ · Resolution done on RESOLVED, failed on UNRESOLVED or DECLINED follow-up date = the day the referral was OPENED + 14 days; shown as 'Follow-up due' until the referral settles

Why this wayEach step is read from the referral, so the chain shows how far a barrier has really got. A referral here is a record, not a transmission: a person sends it and marks it sent. Limits. 'Overdue' takes precedence over 'done', so an ACCEPTED or UNREACHABLE referral past day 14 shows Follow-up overdue beside 'member reached — accepted the referral'. A referral opened without a resource still shows Intervention chosen as done, with 'no resource chosen yet'. The 14 days are a constant and run from opening, not from sending.

In the demoFinding: done, Intervention chosen: open, Referral made: open, Follow-up: open, Resolution: open

Check it yourselfStart a referral on John Doe's barrier: Intervention chosen turns done, and 'Follow-up due' shows the date 14 days out.

app/services/sdoh.py · _chain()app/services/sdoh.py · start()

Appointments N

Care tab, Appointments panel, title

Formula

every appointment booked for the member, in any status (cancelled and no-show included), earliest first

Why this wayAttendance is recorded here but does not close a gap: the gap waits at SERVICE_COMPLETED for clinical evidence. Known inconsistency: if the request fails, the panel shows 0 and 'No appointments' rather than saying it could not load, unlike the other panels on this tab.

In the demo0 appointments

Check it yourself0 in the fresh tenant; after booking in Act 3 it reads 1.

app/api/integration.py · appointments()app/static/app.js · p360Tab()

Documents: count, Size (KB), version (v) and Checksum

Care tab, Documents panel

Formula

count = documents stored for the member size = bytes ÷ 1024, one decimal, labelled KB (strictly KiB) v = the highest earlier version of the same file name for this member + 1, else 1 (the name after unsafe characters are replaced with '_') checksum = the first 16 hex characters of the file's SHA-256

Why this wayA re-upload under the same name is a new version, not an overwrite, and every download re-checks the full SHA-256, so a changed file refuses to serve. Limits: two names that differ only in replaced characters share one version line; and a failed request shows 0 and 'No documents' rather than an error.

In the demoreferral: 0.2 KB, v1, checksum of 16 characters

Check it yourselfOpen Sarah Martinez → Care: one referral letter, 0.2 KB, v1.

app/api/documents.py · upload()app/api/documents.py · list_documents()app/static/app.js · docsPanelHtml()

Team referrals N

Referrals tab, Team referrals panel, title

Formula

every internal referral for the member, in any status (settled included), newest first; a redirect updates the same referral, so it is not counted twice

Why this wayThe member's whole history of hand-offs between teams, not only the open ones; the Due column and the LOOP CLOSED badge say which are still live.

In the demo0 referrals

Check it yourself0 in the fresh tenant; Create referral adds one.

app/services/referrals.py · member_view()

Team referral: Due date (red when overdue)

Referrals tab, Team referrals table, Due column

Formula

due = the date typed in the Create referral form, if any; else the day raised + the queue's SLA (the tenant's referral_sla setting for that queue, else its default, else 3 days); a redirect resets it to that day + the new queue's SLA red when due is before today and the referral is not settled (OUTCOME_CONFIRMED, DECLINED, CANCELLED, UNABLE_TO_CONTACT)

Why this waySee Part I, Referral SLA. Part I leaves out the typed date: its formula holds only when the Due field is left blank. The typed value is not checked to be a date, so text that is not an ISO date is stored as typed and never turns red.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfCreate a referral to Care Management with Due blank: Due is 3 days out, and it turns red on the fourth day if still open.

app/services/referrals.py · create()app/services/referrals.py · sla_days()app/services/referrals.py · _row()

Team referral: LOOP CLOSED badge

Referrals tab, Team referrals table, Status column

Formula

shown when the referral's status is OUTCOME_CONFIRMED; COMPLETED (the receiving team's own report) does not show it; DECLINED, CANCELLED and UNABLE_TO_CONTACT settle the referral without it

Why this wayA loop that closes on the doer's own word measures effort, not outcome. Only a confirmed outcome with evidence closes it, and that evidence is printed in the same row.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfWalk a referral to COMPLETED: no badge. Confirm the outcome with evidence: LOOP CLOSED appears.

app/services/referrals.py · _row()

Vendor referrals N (Referrals tab) and Partner Activity N (Overview)

Referrals tab, Vendor referrals title; Overview, Partner Activity title

Formula

every vendor referral for the member, in any status (confirmed, declined, failed and cancelled included), all time; both panels count the same rows

Why this wayPartner Activity is a rollup of the same records, so the two numbers cannot differ. Community (social) referrals and team referrals are not included; they have their own panels.

In the demo3 vendor referrals; Partner Activity 3

Check it yourselfJohn Doe: MedRide NEMT, Nourish Home Meals and HouseCall Health Assessments on both tabs.

app/services/vendors.py · member_view()

Vendor referral: Progress markers (● ▲ ✕ ○)

Referrals tab, Vendor referrals table, Progress column

Formula

chain: SENT → ACKNOWLEDGED → MEMBER_CONTACTED → SCHEDULED → DELIVERED → CONFIRMED ● every step up to the current status · ▲ the next step when the SLA has passed · ✕ every step after SENT when DECLINED, FAILED or CANCELLED · ○ otherwise

Why this wayDELIVERED is the vendor's claim and CONFIRMED is the plan's evidence, so they are separate steps. The chain is drawn in full, including the part that has not happened. Known defect: for a DECLINED, FAILED or CANCELLED referral the first marker (SENT) is drawn ○, as if the referral was never sent, because those statuses are not positions on the chain. The row reads ○ ✕ ✕ ✕ ✕ ✕.

In the demoMedRide NEMT ● ● ● ● ○ ○, HouseCall Health Assessments ● ● ○ ○ ○ ○, Nourish Home Meals ● ● ● ● ● ●

Check it yourselfHover a marker: the tooltip names the step.

app/services/vendors.py · chain()

Vendor referral: SLA date (red when overdue), also 'SLA due' in a social barrier's vendor box

Referrals tab, Vendor referrals table, SLA column; Care and Referrals tabs, social barrier vendor box

Formula

SLA date = the day the referral was made + the vendor's contracted SLA in calendar days (7 when the vendor has none, or 0) red when the SLA date is before today and the referral is not settled (CONFIRMED, DECLINED, FAILED, CANCELLED) starter contracts: MedRide NEMT 3 · Nourish Home Meals 5 · HouseCall Health Assessments 14 · Regional DME Supply 7 · Bridge Behavioural Network 5

Why this wayThe clock starts at the referral, not at the vendor's acknowledgement, so a vendor who is slow to acknowledge gains nothing. It stops only at a settled status, so SCHEDULED and DELIVERED referrals still go red. That includes a booking the plan made itself. John Doe's MedRide ride is booked for two days after the reset, but its 3-day SLA ends on the reset day. From the next day it shows red, and Partner Activity says 'SLA passed … follow up with the vendor'. Vendor SLAs are per contract. They are not the team-referral SLA in Part I (3 days per queue). Referring the member again to the same vendor for the same service returns the live referral, so its SLA does not restart.

In the demothe reset day

Check it yourselfMedRide NEMT was referred 3 days before the reset with a 3-day SLA, so its SLA date is the reset day.

app/services/vendors.py · refer()app/services/vendors.py · is_overdue()

Partner Activity: Last interaction (date and what happened)

Overview, Partner Activity table

Formula

the event with the latest timestamp in the referral's trail: its date and the label of the status it moved to; with no events, the referral date and 'Referral sent' rows sorted by that timestamp, newest first

Why this wayRead from the event trail, never from a 'last contacted' field somebody has to maintain. Known inconsistency: the date is the first 10 characters of a UTC timestamp. So an event logged after 8 pm Eastern shows the next day, while every other date on this screen is the Eastern business date.

In the demo1 day before the reset

Check it yourselfThe first row, MedRide NEMT 'Service scheduled', matches the newest event in that referral's trail on the Referrals tab.

app/services/vendors.py · _last_interaction()app/services/vendors.py · member_view()

Partner Activity: 'Outcome / next'

Overview, Partner Activity table, Outcome / next column

Formula

settled: the recorded outcome, or 'settled without a recorded outcome' not settled, first that applies: SLA date passed → 'SLA passed <date> — follow up with the vendor' · appointment on file → 'appointment <when>' · else 'awaiting: <next step> · due <SLA date>'

Why this wayWhat happens next is derived from the record, so it cannot go stale. A passed SLA outranks a booked appointment, because the vendor owes a response. The consequence is described under the SLA date above. The Status column and its colour are in Part II, Patient 360: header, gaps, interventions and authorizations.

In the demoMedRide NEMT: appointment, Nourish Home Meals: 14 meals delivered over two weeks; member confirmed, HouseCall Health Assessments: awaiting: vendor reached the member

Check it yourselfHouseCall Health Assessments reads 'awaiting: vendor reached the member · due …'; Nourish Home Meals shows its recorded outcome.

app/services/vendors.py · _next_step()app/static/app.js · p360Tab()

Patient 360: coordination

9 values

The care manager's view of who is already talking to one member and on what authority: the Coordination line in the member header, the contact lists on the Engagement tab, and the disclosure consent a vendor referral needs on the Referrals tab. Every figure below is shown for the demonstration member John Doe, as the care manager Jane Smith sees him.

Coordination: 'N live outreach ladder(s)' (or 'no live outreach')

Member header, Coordination line

Formula

count of the member's outreach ladders whose status is not settled (COMPLETED, EXHAUSTED, STOPPED); every programme's ladders are counted together counted in the browser as the length of the list the server returns; 0 → 'no live outreach'

Why this wayOne number across programmes, because each programme could always see its own outreach and none could see the others'. A ladder counts from the moment it is planned, before anything has been sent: in the fresh demo tenant John Doe has three live ladders (all PLANNED) and no contact history yet, so a live ladder means 'somebody intends to reach this member', not 'somebody has'. The ladder rungs and waits are explained in Part I, Engagement ladder, wait times and the contact ceiling.

In the demo3 live outreach ladders

Check it yourselfopen the Engagement tab; the Outreach panel lists the same ladders. A ladder that is completed, exhausted or stopped drops out of the count.

app/services/engagement.py · who_is_engaging()app/static/app.js · coordinationLine()

Coordination: last decision badge (PROCEED / COMBINE / DELAY / SUPPRESS / ESCALATE), 'with … on …', or 'nothing refused'

Member header, Coordination line

Formula

the newest coordination.decided event in the audit chain for this member (any programme, any age); badge = its decision, hover = its reason, 'with X on D' = what it was combined with the decision itself is the gate's first matching check, in order: do-not-contact → SUPPRESS; no channel usable → SUPPRESS (permanent) or DELAY (temporary); inside quiet hours → DELAY (ESCALATE if the caller marked it critical); a booked appointment or planned contact about something else within lookahead_days (default 2) → COMBINE; another live ladder about something else → COMBINE; caller marked it urgent or critical → ESCALATE; member at the weekly contact ceiling → DELAY; otherwise PROCEED no event → 'nothing refused'

Why this wayThe header replays the gate's own record rather than recomputing it, so it cannot disagree with what the gate actually did. PROCEED is never written to the audit chain, so the badge only ever shows a decision that stopped, folded or escalated something; 'nothing refused' means no such decision has been recorded, not that the member has never been contacted. The decision is shown however old it is (its date is printed beside it). The contact ceiling it uses (4 contacts in 7 days) is in Part I, Engagement ladder, wait times and the contact ceiling; Part I does not yet describe the gate's order of checks. A campaign's fold into another campaign's task is recorded under the same event, so the badge can show COMBINE with task.

In the demonothing refused

Check it yourselfhover the badge for the reason. Try to send outreach to a member who already has a live ladder: the Outreach coordination window shows the full check chain, and after you choose, the header shows that decision.

app/services/coordination.py · latest()app/services/coordination.py · decide()app/static/app.js · coordinationLine()

Coordination: what happened next ('proceeded anyway: …', 'folded into existing work', 'automation skipped this member', 'refused')

Member header, Coordination line, after the badge

Formula

from the same audit event's outcome: overridden → 'proceeded anyway' with the person's recorded reason; combined → 'folded into existing work'; skipped → 'automation skipped this member'; ANY other outcome → 'refused'

Why this wayA person may override COMBINE, DELAY or ESCALATE with a reason of at least 3 characters; SUPPRESS (consent) cannot be overridden. Known defect: a campaign that finds another campaign's task owned by someone else creates its own task anyway and records the outcome kept_separate; the browser has no label for that outcome, so the header says 'refused' for work that in fact went ahead.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnothing to show in the fresh demo tenant: no coordination decision exists after the reset. Override a COMBINE in the Outreach coordination window with a reason; the header then reads 'proceeded anyway' followed by that reason.

app/services/coordination.py · gate()app/services/coordination.py · record_task_combined()app/static/app.js · coordinationLine()

Contact history — all channels (count)

Engagement tab, Contact history panel title

Formula

every message the member was sent or sent back (all channels, both directions) plus every voice call, newest first, cut at 40 the panel is not drawn at all when the count is 0

Why this wayOne list across SMS, email, portal and voice, so 'when did we last speak to this person' needs one screen. The count stops at 40 without saying so: a member with 60 contacts shows 40. Known defect: every finished or abandoned voice call is listed twice, once as the call ('Voice') and once as the transcript message the call writes (labelled lower-case 'voice'), so each call adds 2. The Engagement panel lower on the same tab counts the same contacts differently (see the next rows).

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnothing to show in the fresh demo tenant: nothing has been sent yet, so the panel is hidden. Send one outreach from the ladder; the panel appears with 1. Place a simulated call to the end; the count rises by 2.

app/services/engagement.py · history()app/api/engagement.py · history()app/static/app.js · p360Tab()

Contact history: 'Voice … N member turn(s) · outcome'

Engagement tab, Contact history, each voice call line

Formula

per voice call: N = transcript entries spoken by the member (who == 'member'); the call's outcome is appended when it has one; status is the call's status in lower case

Why this wayMember turns show whether the member actually engaged or the call rang out. The count is of turns, not minutes or words. The transcript message the same call writes is also listed, separately, as its full text.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnothing to show in the fresh demo tenant. Run a simulated call from Engagement → Voice and answer twice; the line reads '… 2 member turn(s)'.

app/services/engagement.py · history()app/services/voice.py · _apply_outcome()

Engagement (count)

Engagement tab, Engagement panel title

Formula

the member's latest 15 message records by time, both directions; voice calls themselves are not included, but the transcript message each call writes is no messages → 'No outreach recorded'

Why this wayThe raw message log, with template and status. It is a different count from Contact history on the same tab: capped at 15 rather than 40, and without call records, so the two titles differ for any member with calls or more than 15 messages. Neither says 'of M'. The channel is printed as stored (lower case) and the timestamp without its year.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfnothing to show in the fresh demo tenant (the panel reads 'No outreach recorded'). After one send, both this panel and Contact history show 1; after one finished call, this shows 1 more and Contact history 2 more.

app/api/vbc.py · get_patient()app/static/app.js · p360Tab()

Consent & Communication Preferences: Channels ('… (opted out)')

Engagement tab, bottom panel

Formula

the four channels sms · email · phone · portal; a channel is marked '(opted out)' only when the stored opt-in is explicitly false

Why this wayA quick read of the stored preference. It does not distinguish 'never asked' from 'opted in': both print the bare channel name. The Consent panel at the top of the same tab does distinguish them (NEVER ASKED), so for John Doe the top panel shows phone as never asked while this line shows 'phone' exactly like the channels he agreed to. Outreach itself is enforced on the server, not from this line.

In the demosms · email · phone · portal

Check it yourselfcompare with the Consent panel at the top of the tab: a channel shown NEVER ASKED there is shown without a mark here.

app/static/app.js · p360Tab()app/services/engagement.py · consent_state()

Create vendor referral: 'Member consent — confirmed' or 'No disclosure consent on file', and whether Send referral is enabled

Referrals tab → Create vendor referral window, consent line

Formula

for the selected need's vendor category: the member's authorization for that category, else their blanket (*) authorization, else none none → never_asked; recorded as declined → refused; expiry date before today → expired (valid through the expiry date itself); expiry not a date → unreadable; otherwise granted Send referral is enabled only for granted; the server applies the same test again when the referral is sent a recorded agreement expires 365 days after it was recorded unless another date is given

Why this waySharing a member's name, address and need with a vendor is disclosure to a third party, a different consent from permission to contact them, so no channel opt-in counts. A category record wins over the blanket one even when it is a refusal or has expired. The window defaults to the first vendor's category (transportation). Known limits: the heading reads 'No disclosure consent on file' for a refusal or an expired authorization as well as for 'never asked' (the reason line underneath says which); the expiry date of a valid authorization is not shown; and an expiry typed in a form is stored without being checked, which is how unreadable arises. Part I's 'Consent disclosure validity 365 days' is this default.

In the demotransportation (never_asked): No disclosure consent on file; Send referral disabled

Check it yourselfopen Create vendor referral for John Doe: the line says no consent on file and Send referral is greyed out. Click 'They agreed'; the line turns to confirmed. Maria Gonzales carries a blanket authorization from the reset and opens confirmed for every need.

app/services/engagement.py · disclosure_state()app/services/engagement.py · record_disclosure()app/api/population.py · disclosure_consent()app/static/app.js · vendorModal()

Next best action

7 values

The recommendation cards on Population (for the VBC manager) and on Patient 360 (for the care manager). Advisory only: nothing happens until a person acts, and every card says why it exists in reasons a care manager can check.

Next best actions (count and order)

Population → Next best actions panel; Patient 360 → Recommended actions

Formula

one recommendation per open care gap (plus an enrolment offer for a high-risk member outside every programme) sorted by band (Critical, High, Medium), then by an internal score, then member name; the top 8 on Population, the top 3 on a member internal score = 10 × risk weight × gap-priority weight × stage factor risk weight: HIGH 1.6 · RISING 1.3 · MEDIUM 1.0 · LOW 0.8 · gap priority: Critical 2.0 · High 1.5 · Medium 1.0 · Low 0.6 stage factor: start 1.0 · retry outreach 0.9 · waiting on an authorization 0.5 (an enrolment offer scores a fixed 12.8)

Why this wayThe score only orders cards within a band and is never shown: a number a care manager cannot interpret is a justification nobody can check. The band and the reasons are what the card shows. See Part I, Next best action.

In the demoJohn Doe — Record HbA1c Control (Critical), John Doe — Complete Diabetic Retinal Exam (Critical), John Doe — Record Blood Pressure Control (Critical), Maria Gonzales — Record HbA1c Control (Critical), Maria Gonzales — Complete Diabetic Retinal Exam (Critical)

app/services/nba.py · recommend()app/services/nba.py · _recommend_for_gap()

Band: CRITICAL / HIGH / MEDIUM PRIORITY, and its one-line justification

Each card, top badge and the line under the title

Formula

Critical: the gap's priority is Critical, or a HIGH/RISING-risk member's gap is 90+ days late High: a HIGH/RISING-risk member, or a High-priority gap, or 90+ days late Medium: everything else (including a gap blocked pending authorization) late = days past the due date once the gap is overdue, days since it opened while it is not yet due an enrolment offer is always High

Why this wayBands are derived from facts, not cut from a score, so each one comes with the sentence that set it ('critical-priority gap on a high-risk member').

In the demoCritical: critical-priority gap on a high-risk member

app/services/nba.py · _band()app/services/nba.py · _gap_age()

'Why Apex recommends this' (the reason lines, each with its source)

Each card, reasons list

Formula

one line per input the engine consumed, in fixed order: Care Gap: 'Overdue N days' (today − due) or 'Open for N days' (today − first event), or 'Due <date> — N days left'; the gap's priority Risk: '<tier>-risk member' · Quality: 'Impacts <measure> — a tracked VBC quality measure with an X% target' Utilization: ED visits and inpatient stays in the last 180 days · Eligibility: 'verified N days ago' (fresh ≤ 30 days) or 'not verified' Outreach: attempts on this gap's ladder · SDOH: the first open barrier and 'N other barriers' · Program/Contract: enrolled or not

Why this wayEight named inputs, every one checkable on the member's record. The card lists which inputs it used, so a missing input is visible, not silent.

In the demoOpen quality gap — HbA1c Control [Care Gap], Overdue 97 days [Care Gap], Critical priority on the measure package [Care Gap], High-risk member [Risk], Impacts Diabetes: HbA1c Control (<8.0%) — a tracked VBC quality measure with a 75% target [Quality], 1 ED visit in the last 6 months [Utilization]

app/services/nba.py · _reasons()

Next step and the card title's verb

Each card

Formula

from the authorization state and the payer's policy for the gap's service: blocked (an undecided authorization) · retry (outreach unreachable or declined) · 'no prior authorization is required' · 'prior authorization is required' · unknown title verb by how the measure is met: Complete (a procedure) · Record (a lab result) · Improve (adherence) · else Close

Why this wayThe next step is the same policy lookup the authorization screens use, so a card can never tell a care manager a service is free of authorization when the policy says otherwise.

In the demoRecord HbA1c Control — No prior authorization is required — this can be scheduled as soon as the member is reached.

app/services/nba.py · _recommend_for_gap()app/services/nba.py · _title()app/services/workflow.py · pa_required()

BLOCKED badge

Each card, top

Formula

the gap's intervention has an authorization with no decision yet

Why this wayA blocked card still shows (the member still needs the care) but it sinks in the order (stage factor 0.5) and its band falls to Medium, because nobody on the care team can move it until the payer decides.

In the demo0 blocked cards in the top 8

app/services/nba.py · _recommend_for_gap()

Action buttons: enabled or disabled, and why

Each card, buttons

Formula

Start Intervention: off when an authorization is pending or an intervention is already running Launch PA: on only when the policy says authorization is required and none is pending Send to Provider: on only when the member has an attributed provider with no live request all off (with the reason) when the signed-in role lacks care-work permission

Why this wayThe button state asks the same permission system the server enforces, so a button that looks enabled always works, and a disabled one says why.

In the demoas the VBC manager: Start Intervention off (your role is read-only for care work); Launch PA off (your role is read-only for care work); Send to Provider off (your role cannot route work to a provider); Assign off (your role cannot assign w

app/services/nba.py · _actions()

'Deferred to <date>' and 'returned' notes

Each card

Formula

dismissing defers a card to today + N days (30 from the card, at most 90) and needs a reason it returns early when its fingerprint changes: gap status · priority · member's risk tier · band · the 30-day bucket of lateness

Why this wayWhat is being deferred is an open gap on a real member, so a deferral expires, and a material change in the case brings the card back with the reason.

In the demo0 deferred in the fresh tenant

Check it yourselfDismiss a card for 30 days, then change the member's gap priority: the card comes back with the reason.

app/services/nba.py · dismiss()app/services/nba.py · fingerprint()

Engagement and voice

22 values

The care manager's outreach screens: the Engagement page (Voice, SMS, Email, Portal and History tabs), the Outreach ladder on a member's Patient 360 Engagement tab, the coordination window that opens when outreach is held, and the HRA findings line on the Care tab. Everything is shown as the care manager Jane Smith sees it; member-level figures are for John Doe unless a row names another member. Campaigns has its own chapter; the consent table, the Coordination line and Contact history are in Patient 360: coordination; the Care management and HRA status panels are in the Patient 360 care chapter.

Outreach sequences (count)

Engagement → History tab, panel title

Formula

the newest 50 OutreachSequence rows in the tenant, by creation time, of every status (settled ladders included) then narrowed to the members this login may see; the number is the length of what is left

Why this wayA sequence is one conversation with one member about one thing, across channels, so this counts conversations, not messages. Two limits. The list stops at 50 without saying '50 of N'. And the 50 are taken before the panel filter, so a panel-scoped login (a provider) can see fewer than 50 rows even when more than 50 of its own members' ladders exist. In the fresh demo the provider sees 5 of the 10.

In the demo10 sequences

Check it yourselfCount the rows in the History table; they match the number in the title.

app/api/engagement.py · sequences()app/static/app.js · engSequences()

SIMULATED DELIVERY badge

Engagement → History tab, panel header; Patient 360 → Engagement tab, Outreach ladder header

Formula

shown when can_really_send() is false can_really_send() is true only when a real messaging provider is configured AND the deployment is not synthetic-only

Why this wayBoth conditions are required, so a synthetic-data deployment can never message a real person even if someone configures a provider. The demo is synthetic-only, so the badge always shows. It is one deployment-wide answer: each sequence also carries its own 'any real send' flag, which no screen renders.

In the demoshown on both

Check it yourselfThe amber badge sits beside the History title and beside the ladder title on John Doe's Engagement tab.

app/services/messaging.py · can_really_send()app/api/engagement.py · sequences()app/api/engagement.py · plan()

Sequence status (PLANNED, ATTEMPTED, ESCALATED, REACHED, SCHEDULED, REMINDED, COMPLETED, EXHAUSTED, STOPPED)

Engagement → History tab, Status column; Patient 360 → Outreach ladder header badge

Formula

set only by the ladder's own actions: opened → PLANNED · a send → ATTEMPTED · moved to the next usable channel → ESCALATED · an outcome that means we spoke to the member → REACHED · a real booked appointment → SCHEDULED · REMINDED · COMPLETED (also set when the care gap closes) STOPPED = the member opted out · EXHAUSTED = no untried channel can ever be used when nothing is usable now: any untried channel blocked only by quiet hours or the contact ceiling → wait; otherwise → EXHAUSTED

Why this wayThe rule separates 'not yet' from 'never'. Quiet hours and the contact ceiling are clocks, so a ladder they block waits instead of being written off. Opt-out, no address on file and do-not-contact are walls, and only walls end a ladder. An EXHAUSTED ladder goes back to PLANNED when the task-rule sweep finds a usable channel again. SCHEDULED cannot be set without a real, uncancelled appointment. When the task-rule sweep runs, a stalled ATTEMPTED or ESCALATED ladder whose wait has passed is escalated automatically. Limit: the event written when a ladder waits always says 'quiet hours', even when the contact ceiling is the cause.

In the demoPLANNED 8, REACHED 2

Check it yourselfHistory: count the Status badges; Maria Gonzales and Elena Petrova are REACHED, the other eight PLANNED.

app/services/engagement.py · open_sequence()app/services/engagement.py · reachability()app/services/engagement.py · escalate()app/services/engagement.py · record_outcome()app/api/engagement.py · step()app/services/task_rules.py · _r_engagement_escalate()app/services/task_rules.py · _r_engagement_exhausted()

Channel (SMS / Email / Portal / Voice)

Engagement → History tab, Channel column

Formula

the ladder's current channel when opened: the first usable channel in the order preferred channel first, then SMS, Email, Portal, Voice afterwards: the channel of the latest send, or the channel escalation moved to (the next usable untried channel in that same order) '—' when the ladder opened with no usable channel

Why this wayThe ladder starts where the member asked to be reached. Part I (Engagement ladder, wait times and the contact ceiling) disagrees. It prints a fixed order starting at SMS and says 'Voice is the last rung, not the first'. In the code Voice is last only among the channels the member did not choose. Sam Okafor's two ladders start on Email because Email is his stated preference, and a member who prefers phone is called first. A preferred channel that is blocked is skipped, not waited for.

In the demoEmail 2, SMS 7, Voice 1 ladders

Check it yourselfHistory: Sam Okafor's rows read Email; Elena Petrova's reads Voice after SMS was tried.

app/services/engagement.py · channels()app/services/engagement.py · open_sequence()app/services/engagement.py · escalate()

Tried ('SMS → Voice') and 'N attempt(s)'

Engagement → History tab, Tried column; Patient 360 → Outreach ladder, Outreach step

Formula

Tried = the distinct channels on which a send was recorded, in the order first used, joined with → N attempt(s) = the number of sends recorded, repeats on the same channel included; hidden when 0

Why this wayOne conversation, several channels: a text on Monday and a call on Wednesday about the same exam are one row, not two. Escalating alone adds nothing to Tried; a channel is added when a send is recorded on it. On this deployment every send is simulated, and the ladder's event says so. A call placed from the Voice tab is not a send on any ladder (see Call status), so it never appears here.

In the demoSMS → Voice · 2 attempt(s)

Check it yourselfHistory: Elena Petrova's row; her Patient 360 ladder's Outreach step reads '2 attempt(s) across SMS, Voice'.

app/services/engagement.py · record_attempt()app/services/engagement.py · _steps()app/static/app.js · engSequences()

Waiting on (amber next act, or 'retry <date>')

Engagement → History tab, Waiting on column

Formula

if the member's recorded answer demands a next act that has not happened: that act (amber badge) else 'retry ' + next attempt date; else '—' next attempt date = the day of the send + the channel's wait (SMS 2, Email 3, Portal 7, Voice 2 days; 3 otherwise); a voicemail restarts that wait from the day it is recorded; escalating sets today; opening a ladder sets today; a reached or wrong-number outcome clears it

Why this wayWaits are Part I's (Engagement ladder, wait times and the contact ceiling). Whether the demanded act has happened is read from the record that would prove it, as in the 'Member said' row. Limit: a PLANNED ladder shows 'retry' plus the day it was opened, although nothing has been sent; there 'retry' means 'first send due'. In the fresh demo all eight PLANNED ladders read 'retry' with the reset day. A wrong number clears the date on purpose, so the escalation rule cannot redial a bad number.

In the demoMaria Gonzales: refer to the contracted transportation vendor; Elena Petrova: book an appointment from an open slot; 8 ladder(s) show a retry date

Check it yourselfHistory: Maria Gonzales and Elena Petrova show amber badges; open their Patient 360 ladders and the 'Member said' line names the same act.

app/api/engagement.py · sequences()app/services/engagement.py · next_trigger()app/services/engagement.py · record_attempt()app/services/engagement.py · record_outcome()

SMS / Email / Portal templates (count) and the template picker

Engagement → SMS, Email and Portal tabs: the templates panel title and the Send picker

Formula

every MessageTemplate row in the tenant whose channel is this tab's: every version, locale and status, not only approved ones the picker lists the same rows as 'key vN — purpose'

Why this wayShowing the library per channel makes 'is there an approved text for this' answerable before a send. Three limits. The note 'No approved … template exists' appears only when the list is empty, so a draft would hide the note and be offered in the picker. The picker does not show locale: the demo SMS list has two lines reading 'T-OUTREACH v1', one en-US and one es-US. And the server ignores the version and locale picked. It sends the highest approved version of that key in the member's own locale, falling back to en-US.

In the demosms 4, email 1, portal 0 templates

Check it yourselfOpen each tab: the panel titles read 4, 1 and 0, and the Portal tab says no approved Portal template exists.

app/api/engagement.py · templates()app/static/app.js · engChannel()app/services/messaging.py · resolve_template()

Send result: 'Recorded on <channel> — simulated, nothing transmitted'

Engagement → SMS, Email and Portal tabs, toast after Send

Formula

channel = the member's preferred channel; if none is recorded, sms the tab the Send button is on is not sent to the server text = the approved template for that channel with {first_name} filled in; nothing else is filled in, because the screen sends no other values

Why this wayKnown defect, stated plainly: the channel tabs do not choose the channel. A send from the Email tab for John Doe, who prefers SMS, is recorded as an SMS, and the toast says 'Recorded on sms'. A template with no approved version for the member's channel is refused. The screen also sends no values, so the demo SMS is stored with the literal text '{org}' and '{service}' in it. Consent, quiet hours and the coordination gate still apply as for any other contact.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfEngagement → Email → pick John Doe → Send, and choose Proceed separately with a reason in the coordination window. The toast names sms. The message then appears in Patient 360 → Engagement with '{org}' unreplaced.

app/services/messaging.py · send_outreach()app/services/messaging.py · render()app/api/engagement.py · send()app/static/app.js · engChannel()

Recent simulated calls (count)

Engagement → Voice tab, bottom panel title

Formula

the newest 20 VoiceCall rows in the tenant, by start time then narrowed to the members this login may see; the number is the length of what is left

Why this wayA short recent list, not a call log: it stops at 20 without saying so. As on History, the 20 are taken before the panel filter, so a provider can see fewer than 20 of their own members' calls when the tenant has more. The fresh demo tenant has no calls; each call placed during the demo adds one.

In the demo0 calls

Check it yourselfPlace a call and finish it: the title goes up by one and the call is the top row.

app/api/voice.py · calls()app/static/app.js · rVoice()

Call status (IN_PROGRESS, COMPLETED, ESCALATED, OPTED_OUT, VOICEMAIL, NO_ANSWER, ABANDONED)

Engagement → Voice tab: the live call's badge and the Status column of Recent simulated calls

Formula

IN_PROGRESS when the call starts when a reply reaches an ending: booked, callback or declined → COMPLETED; escalated → ESCALATED; opted_out → OPTED_OUT; voicemail → VOICEMAIL; no_answer → NO_ANSWER 'Member hangs up' while the call is live → ABANDONED

Why this wayStatus says how the call ended; Outcome says what it achieved, which is why three different results share COMPLETED. The live badge and the table badge are the same stored field. The code also treats RINGING as a live state, but nothing ever sets it. Every ending, abandoned included, writes the transcript to the member's contact history. A call is a separate record from the member's outreach ladder: booking, declining or opting out on a call does not move the ladder, and the ladder's Outreach step still reads 'not attempted'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfStart a call for John Doe, answer 'Yes, please' then 'Mornings': the badge turns COMPLETED and the same status appears in the table.

app/services/voice.py · start()app/services/voice.py · respond()app/services/voice.py · hang_up()app/api/voice.py · calls()

Call outcome text

Engagement → Voice tab: 'Outcome:' under an ended call, and the Outcome column

Formula

booked 'Appointment requested — care team to confirm' · callback 'Member asked to be called back' · declined 'Member declined for now' · escalated 'Transferred to a person' · opted_out 'Member opted out of calls' · voicemail 'Voicemail left' · no_answer 'No answer' · hang-up 'Call ended before it finished' the live panel shows the status when there is no outcome

Why this wayThe outcome is what the workflow acts on. An opt-out sets the member's phone opt-in to 'no', so Voice then reads 'opted out' wherever consent is shown. 'booked' moves the linked intervention from OUTREACH to ENGAGED only when the call was started from an intervention. The Voice tab starts calls without one, so a booking made here books nothing and changes no intervention; the words 'care team to confirm' mean exactly that.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfEnd a call with 'Stop calling me': the outcome reads 'Member opted out of calls', and John Doe's Voice channel then shows opted out on his Engagement tab.

app/services/voice.py · respond()app/services/voice.py · _apply_outcome()app/services/voice.py · hang_up()app/static/app.js · rVoice()

Turns

Engagement → Voice tab, Recent simulated calls, Turns column

Formula

the number of lines in the call's transcript: the opening agent line counts 1, and each member reply adds 2 (the member's line and the agent's answer); a hang-up adds none so turns = 1 + 2 × replies, always odd

Why this wayIt counts spoken lines, not question-and-answer exchanges. A call answered twice ('Yes, please', 'Mornings') shows 5. Patient 360's Contact history describes the same call as '2 member turn(s)': it counts member lines only, so the two screens give different numbers for one call. The scripts list also returns a field named turns that means the number of script steps (9 and 7). It is not shown anywhere.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfOpen the call's Transcript: count the Agent and Member bubbles; the total equals Turns.

app/api/voice.py · calls()app/services/voice.py · respond()app/services/engagement.py · history()

Reply buttons, or 'Outcome' and Close (call ended)

Engagement → Voice tab, the live call panel

Formula

ended = a call is open on screen and its status is neither IN_PROGRESS nor RINGING → show 'Outcome: …' and Close otherwise the buttons = the replies of the script step the call is at, in script order, plus 'Member hangs up'

Why this wayThe member's side is picked from a fixed list, because a simulated call must not imply speech recognition. The server enforces the same rule the screen draws: a reply that is not on the current step's list is refused, and so is any reply after the call has ended ('call already COMPLETED'). Example: the Care gap outreach script opens with five replies: 'Yes, please', 'Not right now', 'Who is this? I want a person', 'Stop calling me', '(no answer)'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfStart a Care gap outreach call: five reply buttons appear; answer until the call ends and they are replaced by the outcome and Close.

app/services/voice.py · options()app/services/voice.py · respond()app/static/app.js · rVoice()

What the agent says (personalised script line)

Engagement → Voice tab, Agent bubbles in the live call and in a transcript

Formula

the approved script's text for the step, with {first_name} = the member's name up to the first space · {org} = the tenant's name, or 'your care team' if it has none · {service} = the Service typed at Start call, or 'a check-up'

Why this wayThe words come from a versioned script a person approved (v1 of each); the system only fills in names. Only the Care gap outreach greeting uses {service}. Replies are rendered without the typed service, which changes nothing today because no later step uses it. The first-name rule is simple: 'Mary Ann Smith' is greeted as 'Mary'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfStart a Care gap outreach call for John Doe with Service 'a diabetic eye exam': the greeting reads 'Hello John, this is an automated call from Demo Health ACO … due for a diabetic eye exam'.

app/services/voice.py · _values()app/services/voice.py · _render()app/api/voice.py · start()

Outreach coordination window: decision (COMBINE / DELAY / SUPPRESS / ESCALATE), 'What the engine checked', and the options

Opens on Start call (Voice), Send (SMS / Email / Portal) and Send outreach (Patient 360 ladder) when the engine does not say PROCEED

Formula

checks in order; the first decisive one wins: do-not-contact → SUPPRESS · no usable channel → SUPPRESS if every channel is blocked by a wall, else DELAY · quiet hours → ESCALATE if marked critical, else DELAY · contact ceiling → warning · an appointment or a ladder send planned within 2 days (tenant setting) about something else → COMBINE · any other live ladder → COMBINE · vendor or provider already involved → warning · marked urgent or critical → ESCALATE · ceiling warning on routine → DELAY · else PROCEED (no window) proceeding anyway needs a reason of 3 or more characters; SUPPRESS cannot be overridden

Why this wayThe window is the gate's refusal, shown with its working, so a person decides and the decision is recorded. The Coordination line in the member header shows the last one. Every door on these screens sends urgency 'routine', so ESCALATE cannot come from them. The Voice and channel tabs also send no purpose, so any live ladder for the member counts as 'something else'. In the fresh demo, Start call for John Doe opens with COMBINE because three of his ladders are planned for today. When the thing to combine with is a ladder, it is always labelled 'Care Management', whichever programme opened it.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfEngagement → Voice → John Doe → Start call: the window shows COMBINE with 'Future contact scheduled' marked. Choose Proceed separately with a reason and the call starts.

app/services/coordination.py · decide()app/services/coordination.py · gate()app/static/app.js · coordModal()

Outreach ladder: which ladder is shown, and NONE RUNNING

Patient 360 → Engagement tab, Outreach ladder header

Formula

the member's newest ladder (by creation time) whose status is not COMPLETED, EXHAUSTED or STOPPED none → NONE RUNNING, and no progress bar

Why this wayThe panel shows one conversation, the newest one still live. A member with several live ladders shows only one here, and History lists them all. A finished ladder is never shown, so a ladder that just COMPLETED makes the panel read NONE RUNNING, not a full bar.

In the demoPLANNED, one of 3 live ladders

Check it yourselfEngagement → History: John Doe has three rows; his Patient 360 ladder shows one of them.

app/api/engagement.py · plan()app/static/app.js · engagementLadder()

Outreach ladder: 'X of 7 steps complete' and the progress bar

Patient 360 → Engagement tab, Outreach ladder, bar under the header

Formula

X = the steps whose state is 'done'; the total is all 7 steps bar = round(100 × X / 7)%; green at 80% or more, amber at 40% or more, else red 'not needed', 'waiting', 'blocked' and 'failed' do not count as done

Why this wayKnown limit: a member reached on the first channel has Escalation 'not needed', which still counts in the 7. Only live ladders are shown, and a completed ladder leaves the panel. So a first-channel success tops out at REMINDED: 5 of 7 (71%, amber). A ladder that needed escalation reaches 6 of 7 (86%, green) at the same point, so the member who answered the first text looks less complete. The Transitions plan bar drops 'not needed' steps from its total; this bar does not.

In the demo1 of 7 steps complete, bar 14% (red)

Check it yourselfCount the ticked steps in the chain under the bar.

app/services/engagement.py · plan()app/static/app.js · engagementLadder()

Outreach ladder: the seven steps and their state (done / open / waiting / blocked / failed / not needed)

Patient 360 → Engagement tab, Outreach ladder chain

Formula

stage rank: PLANNED 1, ATTEMPTED 2, ESCALATED 3, REACHED 4, SCHEDULED 5, REMINDED 6, COMPLETED 7 (EXHAUSTED and STOPPED 3) Preferred channel: done if the preferred channel, else the first usable one, exists; blocked if none · Outreach: done from rank 2 · Response: done from rank 4, failed if EXHAUSTED or STOPPED, else waiting from rank 2 · Escalation: not needed if reached on one channel, done if more than one channel was tried · next step: done when the member's answer was acted on (see 'Member said'); with no answer recorded, done from rank 5 · Reminder: from rank 6 · Completion: at 7

Why this wayThe step after Escalation is named after what the member's answer demands, for example 'Vendor referral', and not always 'Scheduling'. Its done flag is read from the record that proves it. Limit: a PLANNED ladder shows Response as open but with the detail 'waiting until <opening day>', although nothing has been sent (John Doe's reads so on the reset day).

In the demoPreferred channel done, Outreach done, Response done, Escalation not_needed, Vendor referral open, Reminder open, Completion open

Check it yourselfMaria Gonzales, Engagement tab: Escalation shows a dash ('the member answered on the first channel') and Vendor referral is still open.

app/services/engagement.py · _steps()app/static/app.js · engagementLadder()

Outreach ladder: each channel 'available' or the reason it is not

Patient 360 → Engagement tab, Outreach ladder, channel list (preferred first)

Formula

checked in order, first failure shown: do-not-contact · opted out of the channel · no recorded consent where the tenant requires it (SMS, Voice) · contact ceiling · quiet hours · no address on file (where the member has an address record) contact ceiling = 4 outbound contacts in the last 7 days (or the member's own limit) holds automated outreach until the oldest contact + 7 days

Why this wayOne rule set for every channel, voice included; see Part I, Engagement ladder, wait times and the contact ceiling. This list asks as automation does, so the ceiling shows as a hold. Part I and the code disagree on the count. The ceiling counts outbound messages PLUS every simulated call, and each finished call also writes an outbound voice message. So one call counts twice. Tested on a fresh tenant, two calls the member hung up on put John Doe at '4 times in 7 days by other programmes', and every channel was held for a week. The coordination service's own 'contacts in the last 7 days' counts the same week as 2; no screen shows that number. The text says 'other programmes' but counts the care manager's own contacts too.

In the demoSMS available, Email available, Portal available, Voice available

Check it yourselfSam Okafor's list reads 'no mobile on file' for SMS; Derrick Boateng's reads 'asked not to be contacted at all' on every channel.

app/services/engagement.py · contactability()app/services/engagement.py · _recent_contacts()app/services/engagement.py · channels()app/services/voice.py · _apply_outcome()

'Member said: …' with TRIGGERS <act> (amber) or <act> DONE (green), and its evidence

Patient 360 → Engagement tab, Outreach ladder, under the channel list

Formula

each of the 12 recorded outcomes names one next act; done is read from the record that would prove it: scheduling = a linked appointment that is not cancelled · vendor referral = one opened from this ladder · evidence = the gap CLOSED with evidence · benefits check, prior auth, clinical referral = ANY such record the member has · escalate = more than one channel tried · settle = ladder finished · wait = finished or REACHED · contact details, reminder and next best action = SCHEDULED, REMINDED or finished · a bare 'reached' = never

Why this wayThe ladder's own status never vouches for itself: 'appointment booked' is printed only when an appointment row exists. Limit: the benefits check, prior auth and clinical referral tests accept any such record the member has ever had. An eligibility check or authorization from last year marks today's 'Cost concern' or 'PA required' as done; only scheduling, vendor referral and evidence are tied to this ladder.

In the demoTransportation barrier: TRIGGERS VENDOR REFERRAL

Check it yourselfMaria Gonzales: the amber badge and a button to refer her to the transportation vendor; after the referral is sent the badge turns green.

app/services/engagement.py · next_trigger()app/static/app.js · engTriggerBanner()

Health risk assessment: 'What it found'

Patient 360 → Care tab, Health risk assessment table, last column

Formula

the assessment's stored outcome; for a completed HRA with none, '—' when an HRA is completed with answers and nobody typed an outcome, the stored line is 'P positive finding(s) — <findings>; D of M actioned' or 'A items answered, no positive findings' A = answers to the 8 instrument questions (unknown keys dropped) · positive = 'yes' (also y, true, 1), except the housing question, where 'no' is the finding · M = actions tried, one per positive finding · D = those that succeeded

Why this wayUnlike most values in this paper, the line is a snapshot written once at completion and never recomputed. A referral later closed or refused does not change it. A typed outcome always wins: refused and unreachable HRAs must carry a reason, and that reason is what shows here. Linda Tran's refused HRA shows 'member declined the HRA'. 'D of M' can be below M on purpose: a transport referral needs disclosure consent, and without it the finding is recorded but not actioned.

In the demoREFUSED: member declined the HRA

Check it yourselfComplete John Doe's HRA with two Yes answers: the table's last column shows the one-line summary, and the assessment's event trail carries the same line.

app/services/hra.py · summary()app/services/hra.py · _is_positive()app/api/population.py · assessment_step()app/static/app.js · hraPanel()

Assessment completed window: 'N positive finding(s)', Started, Could not start

Patient 360 → Care tab → Complete HRA → Complete assessment (result window)

Formula

N = the answers judged positive (same rule as 'What it found'), counted in the browser Started = the actions that succeeded, with what was opened; Could not start = the ones that failed, with the reason

Why this wayThe window shows what the answers set off, not only that they were saved: each positive answer opens a referral, a vendor request or a task through that module's own service. A failed action is shown with its reason and the finding stays on the record, so the social-needs rules keep chasing it. An HRA cannot be completed with no answers.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfAnswer Yes to transportation and food and complete: the window lists what started and anything that could not start (for example, a transport referral with no disclosure consent).

app/static/app.js · hraSubmit()app/services/hra.py · extract()app/services/hra.py · generate()

Transitions of care

9 values

The care manager's list of recent hospital discharges, each with its follow-up clocks (contact within 48 hours, a visit within 7 days, readmission watch for 30 days) and a transition plan whose every step is read from the work beneath it.

Discharges (and the 'Recent discharges' count)

KPI card 1; list title

Formula

inpatient encounters discharged 0 to 120 days ago

Why this way120 days is how long a discharge stays actionable (Part I, Transition-of-care windows). Only inpatient stays count, and the Command Center's 'Discharges (30d)' card counts this same list's last 30 days. For a provider login the list and the cards above it both count the provider's panel only.

In the demo1 discharges

app/services/transitions.py · recent_discharges()app/services/transitions.py · summary()

Contact window open, Visit window open, Window missed

KPI cards 2–4; Status column (CONTACT DUE / VISIT DUE / WINDOW MISSED)

Formula

age = today − discharge date (days) contact window open when age ≤ 2 (48 hours) · visit window open when age ≤ 7 · window missed when age > 7 status badge: WINDOW MISSED if missed, else CONTACT DUE if the contact window is open, else VISIT DUE

Why this wayBoth clocks run from the DISCHARGE date, not from when the record reached the platform, so a late feed cannot buy time. A missed window is shown as missed, not as late success: a 48-hour call made on day three is a missed measure.

In the democontact open 1, visit open 1, missed 0

app/services/transitions.py · recent_discharges()

Contact by, Visit by

Recent discharges table

Formula

contact by = discharge date + 2 days (48 hours) · visit by = discharge date + 7 days

Why this wayCalendar-day deadlines a person can act on. Known inconsistency: these two columns and the KPI cards use fixed 48 h / 7 d, while each plan step's deadline is read from the tenant's own task-rule settings; a tenant that changes its rules changes the plan but not these columns.

In the demodischarged 2 days ago: contact by +2 d, visit by +7 d

app/services/transitions.py · recent_discharges()

Readmitted ≤30d (card and READMITTED badge)

KPI card 5; Status column

Formula

a discharge followed by another INPATIENT admission of the same member 1–30 days later (a same-day admission is not a readmission)

Why this wayA per-discharge flag and a count over the 120-day list; it can still rise for a discharge whose 30 days have not passed. This is a different figure from Finance's 30-day readmission RATE, which judges only discharges whose window has fully elapsed.

In the demo0 readmitted

app/services/transitions.py · recent_discharges()

Plans with a missed step

KPI card 6

Formula

discharges whose transition plan has at least one step in state 'missed' (counted in the browser over the listed plans)

Why this wayThe card that says whether anything has already fallen through, as opposed to what is still due.

In the demo0 plans

app/static/app.js · rTransitions()

Transition plan: 'X of Y actions completed' and its bar

Under each discharge

Formula

Y = the plan's steps except readmission monitoring, the closing step and steps not needed (the discharge itself is counted, as a step already done) X = those steps done or dismissed bar = round( 100 × X ÷ Y ) %; green ≥ 80, amber ≥ 40, red below

Why this wayProgress leaves out the steps nobody can act on (the 30-day watch, the close, a step not needed). A dismissed step counts as settled because someone recorded a decision on it. In the demo: discharge, contact, medication reconciliation and barriers are done, 4 of 7.

In the demo4 of 7 actions completed

app/services/transitions.py · plan()app/static/app.js · transitionPlan()

Transition plan: each step's state (done / open / overdue / missed / not needed …)

Under each discharge, the step chain

Formula

a task step takes its task's state: done, dismissed, open, or overdue (open with due date before today) no task and today past the step's deadline → missed ('window closed <date>, N days ago') step deadline, from the tenant's task rule: discharge + ⌈contact hours ÷ 24⌉ days, or + visit days, or + days Eligibility / PA: approved → done, denied → failed, pending → open, none raised → not needed Visit completion: read from the appointment (completed → done, booked → open)

Why this wayNo step is ticked by hand: each is read from the task, authorization or appointment it depends on, so the plan cannot claim more than has happened.

In the demoDischarge done, Contact done, Medication reconciliation done, Clinical assessment open, Follow-up appointment open, Barriers / transport done, Eligibility / PA not_needed, Visit completion open, Readmission monitoring watching, Transition closed open

app/services/transitions.py · plan()app/services/transitions.py · _step_deadline()

Transition plan: 'N missed' and 'N overdue' badges

Transition plan header

Formula

missed = actionable steps in state missed · overdue = steps whose task is open past its due date

Why this wayTwo different failures: overdue can still be done, missed cannot.

In the demomissed 0, overdue 0

app/services/transitions.py · plan()

Readmission monitoring ('N days left') and the plan's close state

Transition plan, last steps

Formula

watch ends at discharge + 30 days; 'N days left' = watch end − today; done once today ≥ watch end plan close state: failed if readmitted · open if any step is open, overdue or not raised · watching while the 30-day watch runs · otherwise done

Why this wayA transition is not closed at the follow-up visit; it is closed when the member has stayed out of hospital for 30 days, which is what the plan is for.

In the demo28 days left in the window

app/services/transitions.py · plan()

Population

11 values

The VBC manager's population view: every measure's denominator and numerator, saved cohorts, attribution (who the contract holds you responsible for), where cost and opportunity concentrate, and how each provider group performs.

Denominator (per measure)

Quality measures table

Formula

members whose age today is within the measure's age range AND who have an active condition from the measure's denominator code list, minus excluded members

Why this wayEligibility is read from the clinical record at request time, never from a stored flag, so a new diagnosis puts a member into the denominator on the next read. The rates are evaluated over every member in the tenant, not only the attributed population. See Part I, One member, one measure.

In the demoDiabetes: HbA1c Control (<8.0%) 6, Diabetes: Eye Exam Performed 6, Statin Adherence (PDC >= 80%) 8, Controlling High Blood Pressure (<140 systolic) 10, Annual Wellness Visit 9

app/services/quality.py · evaluate_tenant()app/services/quality.py · in_denominator()

Numerator (per measure)

Quality measures table

Formula

denominator members who comply: lab measures: latest qualifying result within the look-back (365 days by default) is at or below the measure's threshold procedure measures: a qualifying procedure within the look-back medication measures: the best active qualifying medication's adherence (PDC) ≥ 80%

Why this wayThree kinds of evidence, one rule each, all read from the record. Known defect being fixed: a lab result EXACTLY at the threshold (HbA1c 8.0, systolic 140) currently counts as controlled, although the measure titles say '<8.0%' and '<140'.

In the demoDiabetes: HbA1c Control (<8.0%) 2, Diabetes: Eye Exam Performed 0, Statin Adherence (PDC >= 80%) 2, Controlling High Blood Pressure (<140 systolic) 4, Annual Wellness Visit 2

app/services/quality.py · meets_numerator()

Excluded (per measure)

Quality measures table

Formula

denominator members with an active condition from the measure's exclusion code list

Why this wayExcluded members leave the denominator entirely rather than counting as failures, as measure specifications require.

In the demo0 excluded across all measures

app/services/quality.py · excluded()

Open gaps and Rate % (per measure) and its bar colour

Quality measures table

Formula

open gaps = denominator − numerator rate % = round( 100 × numerator ÷ denominator, 1 ); blank with no denominator bar: green when rate ≥ the measure's target · amber within 15 points below it · red further below · blue with no target

Why this wayThe same rate as the Quality screen, from the same function, coloured by the same rule against the same target (the measure package's), so a rate is one colour everywhere it appears.

In the demoDiabetes: HbA1c Control (<8.0%): 4 open, 33.3%, Diabetes: Eye Exam Performed: 6 open, 0.0%, Statin Adherence (PDC >= 80%): 6 open, 25.0%, Controlling High Blood Pressure (<140 systolic): 6 open, 40.0%, Annual Wellness Visit: 7 open, 22.2%

app/services/quality.py · evaluate_tenant()app/static/app.js · rPopulation()

Cohorts: Members (size)

Cohorts table

Formula

members matching EVERY filter of the saved definition: risk tier in the list · PCP equals · an active condition in the code list · enrolled in a listed programme · has (or has no) open gap, optionally on named measures

Why this wayA cohort stores its definition, not its members, so its size is re-evaluated on every read and a member enters or leaves as their record changes.

In the demoRising and high risk 6, Diabetes population 6, Diabetes with open gaps 6, Uncontrolled hypertension 6

app/services/population.py · cohort_members()

Attribution: 'N members attributed'

Attribution panel

Formula

attribution rows in the current open period (the period containing today)

Why this wayAttribution decides accountability, so each member's method and rationale are shown beside the count. Known inconsistency: this count includes rows that have ended, while the Attribution summary's 'Total attributed' excludes them (they agree today because nothing ends an attribution yet).

In the demo12 members

app/services/population.py · current_period()

Attribution summary: Total, Added this period, Removed this period, per provider

Attribution summary panel

Formula

total = current-period attributions with no end date · added = those dated on or after the period start · removed = those with an end date · per provider = count of current attributions naming that provider

Why this wayRemoved is a MEASURED zero, stated as such: no de-attribution path writes an end date yet. Known limit: every attribution is stamped with the period start date, so 'Added this period' currently always equals 'Total'.

In the demototal 12, added 12, removed 0; Dr. P. Osei 5, Dr. Robert Jones 3, Dr. Sarah Smith 2, Dr. Karen White 2

app/services/vbc_manager.py · attribution_summary()

Unattributed members (and the reason)

Attribution summary panel

Formula

members with no current attribution; reason 'no declared PCP' or 'PCP on record but not attributed'

Why this wayThe members nobody is accountable for are the ones most likely to be missed, so they are listed by name with the reason.

In the demoJon Doe (PCP on record but not attributed)

app/services/vbc_manager.py · attribution_summary()

Highest-opportunity members: Cost, Primary driver, Open gaps (unworked), In flight, ranking

Highest-opportunity members panel

Formula

cost = the member's allowed $ in the period · primary driver = the place of service with the most allowed $ unworked = open gaps with no live intervention opportunity score = round( (cost ÷ 1000) × (1 + unworked × risk weight), 1 ), risk weight HIGH 1.6 · RISING 1.3 · MEDIUM 1.0 · LOW 0.8 top 8 by score (the score itself is not shown)

Why this waySpend alone ranks members nobody can help; open gaps alone ignore cost. Multiplying them puts first the members where money is being spent AND something can still be done. A member with no unworked gaps still ranks, on cost alone.

In the demoJohn Doe: $17135.0, Professional / office, 5 unworked, score 154.2, Maria Gonzales: $17135.0, Professional / office, 5 unworked, score 154.2, Sam Okafor: $25550.0, Inpatient, 1 unworked, score 58.8, Linda Tran: $9385.0, Professional / office, 4 unworked, score 58.2

Check it yourselfJohn Doe: 17,135 ÷ 1000 × (1 + 5 × 1.6) = 154.2.

app/services/vbc_manager.py · concentration()

'Why ranked here'

Highest-opportunity members panel

Formula

'$<spend> spend, N gap(s) with no intervention running, <tier> risk' (or the in-flight / no-gap variant)

Why this wayBuilt from the same three inputs as the ranking, so the sentence is the ranking's own reason.

In the demo$17,135 spend, 5 gap(s) with no intervention running, high risk

app/services/vbc_manager.py · _concentration_why()

Provider performance: Attributed, High / rising, Open gaps, Gap closure %, PMPM

Provider performance panel

Formula

grouped by attributed provider (else declared PCP, else 'Unattributed') gap closure % = round( 100 × closed ÷ (closed + open), 1 ) PMPM = round( the group's period allowed $ ÷ the group's member months, 2 ) rows sorted by open gaps, most first

Why this wayThe same PMPM arithmetic as the contract, narrowed to one group's members, so group PMPMs are comparable with the contract's. Closed gaps are counted all-time, so a new period does not reset closure credit.

In the demoDr. P. Osei: 5 attributed, 3 high/rising, 16 open, 0.0% closed, $1091.21 PMPM, Dr. Robert Jones: 3 attributed, 2 high/rising, 6 open, 0.0% closed, $1555.56 PMPM, Dr. Sarah Smith: 2 attributed, 1 high/rising, 5 open, 0.0% closed, $2948.92 PMPM, Dr. Karen White: 2 attributed, 0 high/rising, 1 open, 0.0% closed, $1315.64 PMPM

app/services/vbc_manager.py · provider_performance()

Quality

16 values

The VBC manager's quality screen: every measure's rate against its target, how many composite points each open gap is worth, and a what-if model of closing more gaps before committing a team to the work.

Quality opportunity (measure count)

Quality opportunity panel, title count

Formula

number of measures in the tenant's active measure package, rated or not

Why this wayEvery active measure is listed, including one with no eligible members yet, so nothing disappears from the table because it is empty.

In the demo5 measures

app/services/vbc_manager.py · quality_opportunity()

Performance (rate %) and its bar colour

Quality opportunity table, Performance column

Formula

rate % = round( 100 × N ÷ D, 1 ) D = members in the measure's denominator and not excluded, N = those meeting the numerator bar: green when rate ≥ target · amber when within 15 points below target · red further below · blue when there is no target

Why this wayThe rate is recomputed from the members every time the screen is read, never stored, so it cannot go stale. See Part I, A measure across a population. The 15-point amber band is a display convention only; it changes no number.

In the demoDiabetes: Eye Exam Performed 0.0% (0 of 6), Annual Wellness Visit 22.2% (2 of 9), Statin Adherence (PDC >= 80%) 25.0% (2 of 8), Diabetes: HbA1c Control (<8.0%) 33.3% (2 of 6), Controlling High Blood Pressure (<140 systolic) 40.0% (4 of 10)

Check it yourselfClick 'Members' on a row: the drill lists the D members and marks the N who meet it.

app/services/quality.py · evaluate_tenant()app/static/app.js · rateBarHtml()

Target

Quality opportunity table, Target column

Formula

the measure package's configured target (stored, versioned with the package); 'no target set' when none

Why this wayTargets come from the versioned measure package, not from this report. A measure with no target still shows its rate and gaps, but no shortfall: comparing a rate with a target nobody agreed would manufacture one.

In the demoDiabetes: Eye Exam Performed 70.0%, Annual Wellness Visit 60.0%, Statin Adherence (PDC >= 80%) 80.0%, Diabetes: HbA1c Control (<8.0%) 75.0%, Controlling High Blood Pressure (<140 systolic) 75.0%

app/services/vbc_manager.py · quality_opportunity()

Gap to target ('N pts short' / 'met')

Quality opportunity table, Gap to target column

Formula

gap to target = round( target % − rate %, 1 ) percentage points 'N pts short' when above 0, 'met' otherwise; blank when there is no target or no rate

Why this wayPoints here are percentage points of THAT measure's rate, not composite points; the next column converts to composite points.

In the demoDiabetes: Eye Exam Performed 70.0 pts short, Annual Wellness Visit 37.8 pts short, Statin Adherence (PDC >= 80%) 55.0 pts short, Diabetes: HbA1c Control (<8.0%) 41.7 pts short, Controlling High Blood Pressure (<140 systolic) 35.0 pts short

app/services/vbc_manager.py · quality_opportunity()

'N member(s) would close it'

Quality opportunity table, under Gap to target

Formula

members to target = max( ⌈ D × target ÷ 100 ⌉ − N, 0 )

Why this wayThe smallest whole number of additional members who must meet the measure for the rate to reach the target. Rounded UP, because a fraction of a member cannot close a gap.

In the demoDiabetes: Eye Exam Performed 5, Annual Wellness Visit 4, Statin Adherence (PDC >= 80%) 5, Diabetes: HbA1c Control (<8.0%) 3, Controlling High Blood Pressure (<140 systolic) 4

Check it yourselfEye exam: D = 6 and target 70%: ⌈6 × 0.70⌉ = 5 members; N = 0, so 5.

app/services/vbc_manager.py · quality_opportunity()

Open gaps (per measure)

Quality opportunity table, Open gaps column

Formula

open gaps = D − N (members in the denominator who do not meet the numerator)

Why this wayCounted by the measure engine from members, not from stored gap records, so it is what the rate implies. The drill behind 'Members' lists the same members, and a guard fails if the two ever disagree.

In the demoDiabetes: Eye Exam Performed 6, Annual Wellness Visit 7, Statin Adherence (PDC >= 80%) 6, Diabetes: HbA1c Control (<8.0%) 4, Controlling High Blood Pressure (<140 systolic) 6

app/services/quality.py · evaluate_tenant()

'each closure adds N composite points' (points per gap)

What if we close more gaps? panel, each row

Formula

points per gap = 100 ÷ D ÷ n, shown to 2 decimals D = that measure's denominator, n = number of RATED measures (those with a rate)

Why this wayTwo facts in sequence: closing one gap moves that measure's own rate by 100 ÷ D points, and the composite is the plain average of n measures, so it moves by that ÷ n. Small denominators move the composite fastest per gap, which is a property of the contract's scoring method, not a statement about which members matter most. See Part I, Quality opportunity and points per gap.

In the demoDiabetes: Eye Exam Performed 3.33, Annual Wellness Visit 2.22, Statin Adherence (PDC >= 80%) 2.5, Diabetes: HbA1c Control (<8.0%) 3.33, Controlling High Blood Pressure (<140 systolic) 2.0

Check it yourselfEye exam: 100 ÷ 6 ÷ 5 = 3.33.

app/services/vbc_manager.py · quality_opportunity()

'N composite pts' (points available)

Quality opportunity table, under the Opportunity band

Formula

points available = round( (100 ÷ D ÷ n) × open gaps, 2 ) multiplied from the EXACT per-gap value and rounded once, at the end rows are sorted by points available (largest first), then title

Why this wayThe composite points the score would gain if every open gap in that measure closed. Rounded once at the end, so it equals what the scenario engine answers for closing the same gaps (a guard compares the two).

In the demoDiabetes: Eye Exam Performed 20.0, Annual Wellness Visit 15.56, Statin Adherence (PDC >= 80%) 15.0, Diabetes: HbA1c Control (<8.0%) 13.33, Controlling High Blood Pressure (<140 systolic) 12.0

Check it yourselfEye exam: 100 ÷ 6 ÷ 5 × 6 = 20.0 (not 3.33 × 6 = 19.98).

app/services/vbc_manager.py · quality_opportunity()

Opportunity (High / Medium / Low)

Quality opportunity table, Opportunity column

Formula

Low when the measure is already at or above its target otherwise High when points available ≥ 3.0 · Medium when ≥ 1.0 · Low below 1.0 (a measure with no target is banded on points alone)

Why this wayStated thresholds rather than a feeling: a band is a reading aid over the points-available number, and says nothing the number does not.

In the demoDiabetes: Eye Exam Performed High, Annual Wellness Visit High, Statin Adherence (PDC >= 80%) High, Diabetes: HbA1c Control (<8.0%) High, Controlling High Blood Pressure (<140 systolic) High

app/services/vbc_manager.py · _opportunity_band()

Quality now (composite)

What if we close more gaps? result card; also Contract Health

Formula

composite % = round( mean( rate % of every rated measure ), 1 )

Why this wayThe single number the contract settles on: an unweighted mean of the rated measures, computed in one place (quality.composite) and read by every screen that shows it. A measure with no eligible members has no rate and is left out of the mean rather than counted as zero. See Part I, The quality composite.

In the demo24.1 %

app/services/quality.py · composite()app/services/vbc_manager.py · scenario()

Quality after (scenario)

What if we close more gaps? result card

Formula

for each rated measure: closures = min( requested, open gaps ); rate after = round( 100 × (N + closures) ÷ D, 1 ) quality after = round( mean( rate after ), 1 ) (each measure's rate is rounded to 1 decimal before the mean, exactly as the live composite is computed)

Why this wayThe contract engine's own arithmetic with the numerators moved; nothing is stored and nothing is predicted. Denominators do not move and closures are capped at the gaps actually open. The demo value models closing every open gap in the top-opportunity measure.

In the democlose all 6 Diabetes: Eye Exam Performed gaps: 24.1% → 44.1%

Check it yourselfEnter the same closures in the panel and press Model this.

app/services/vbc_manager.py · scenario()

'Composite moves ±N points'

What if we close more gaps? result sentence

Formula

moves = quality after − quality now, both already rounded to 1 decimal, shown to 1 decimal with its sign (computed in the browser)

Why this wayIt is the difference of the two cards above it, so it always matches them, even where it differs by 0.1 from (points per gap × closures) because each side was rounded first.

In the democlose one Diabetes: Eye Exam Performed gap: +3.3 points

app/static/app.js · scenarioResult()

Gate, and 'This clears / still does not clear the quality gate'

What if we close more gaps? result card and sentence

Formula

gate = the active contract's minimum quality % (a contract term) clears the gate when quality after ≥ gate

Why this wayThe gate is read from the contract, never set on this screen. Clearing it is what turns earned savings into a payment; see Part I, Contract performance and settlement.

In the demogate 60.0%; the gate plan (12 closures) gives 62.4%, clears: yes

app/services/vbc_manager.py · scenario()app/services/finance.py · contract_performance()

Settlement change

What if we close more gaps? result card

Formula

settlement after = round( gross savings × shared-savings rate × (1 − quality withhold), 2 ) when quality after ≥ gate and gross savings > 0, otherwise 0 settlement change = settlement after − settlement now cost performance is held at its current value

Why this wayIt models quality only: the savings already generated are held constant and the question is whether the gate now releases them. It is an estimate; the payer reconciles against run-out claims months later.

In the demowith the gate plan: $0.0 → $9770.69 (change $9770.69)

app/services/vbc_manager.py · scenario()app/services/vbc_manager.py · _rate()

Closures modelled, Rate before, Rate after (capped)

What if we close more gaps? result table

Formula

closures modelled = min( requested, open gaps ), '(capped at gaps open)' when more were requested rate before = the measure's rate now; rate after = round( 100 × (N + closures) ÷ D, 1 )

Why this wayYou cannot close more gaps than are open, so a larger request is capped and says so rather than producing a rate above 100%.

In the demoDiabetes: Eye Exam Performed: 6 modelled (capped), 0.0% → 100.0%

app/services/vbc_manager.py · scenario()

'Fill with the gate plan' (pre-filled closures)

What if we close more gaps? panel button

Formula

the gate plan: take measures in order of points per gap (highest first); from each close min( open gaps, ⌈ remaining shortfall ÷ points per gap ⌉ ) until the gate is reached

Why this wayGreedy is optimal here, not a heuristic: every gap in a measure is worth the same number of composite points, so the highest-yield gaps first is the fewest closures that clear the gate. See Part I, Path to the quality gate.

In the demoDiabetes: Eye Exam Performed 6, Diabetes: HbA1c Control (<8.0%) 4, Statin Adherence (PDC >= 80%) 2

app/services/vbc_manager.py · gate_path()

Contract Health

16 values

The VBC manager's (and executive's) view of the active value-based contract: is it earning, what is binding it, the path to the quality gate, how each measure is trending, and today's priorities.

N attributed members

Contract banner

Formula

members attributed to the contract in the current contract period (if no period is open: every member in the tenant)

Why this wayA shared-savings contract pays on the members attributed to it, so cost and savings are computed over this population. The Command Center's 'Members' card counts everyone on file, so the two numbers differ by design.

In the demo12 members

app/services/vbc_manager.py · contract_health()app/services/population.py · attributed_patients()

Headline sentence (the binding constraint)

Contract banner, bold sentence

Formula

cost is favourable when actual PMPM ≤ benchmark; quality is met when composite ≥ gate one of four sentences: quality binding (cost ok, quality not) · both met (states the settlement) · both missed · cost binding (quality ok, cost not)

Why this wayA leader's first question is 'which of the two gates is stopping us'. The sentence answers it from the same numbers as the cards below it, so it can never contradict them.

In the demoCost performance is favourable — $20,570 of savings generated — but none of it is earned, because quality is 24.1% against a 60.0% gate. Quality is the binding constraint, not cost.

app/services/vbc_manager.py · _headline()

Actual PMPM (vs benchmark, favourable / unfavourable)

Contract Health card 1

Formula

actual PMPM = round( allowed $ of attributed members' claims with a service date in [period start, min(period end, today)] ÷ member-months, 2 ) member-months = Σ over the period's attribution rows of months between max(attributed, start) and min(end, today); a part month = days ÷ 30.44 favourable when benchmark − actual ≥ 0

Why this wayAllowed dollars, not paid, because the contract benchmark is set on allowed cost. Member-months counts partial months, so a member attributed mid-period costs only their share of the denominator. See Part I, Contract performance and settlement.

In the demo$1,554.32 against a $1,750.00 benchmark — under by $195.68

Check it yourselfFinance → Cost: the same PMPM and member-months are shown with the claims behind them.

app/services/finance.py · cost_summary()app/services/finance.py · contract_performance()

Gross savings

Contract Health card 2

Formula

gross savings = round( round(benchmark − actual PMPM, 2) × member-months, 2 )

Why this waySavings the population generated against the benchmark, before the contract decides how much of it is earned. The per-member variance is rounded to cents before it is multiplied, exactly as it is shown, so anyone can reproduce it from the card: 195.68 × 105.12.

In the demo$20569.88 (Variance against benchmark across 105.12 member-months)

app/services/finance.py · contract_performance()

Quality score (vs gate, on track / at risk)

Contract Health card 3

Formula

composite = round( mean( rate % of every rated measure ), 1 ), each rate already rounded to 1 decimal on track when composite ≥ the contract's minimum quality %, otherwise at risk

Why this wayOne definition read by every screen (quality.composite). Known limit, stated rather than hidden: the measure rates are evaluated over every member in the tenant, while the cost cards on this screen use the attributed members. See Part I, The quality composite.

In the demo24.1% against a 60.0% minimum to earn any savings

app/services/quality.py · composite()app/services/finance.py · contract_performance()

Estimated settlement

Contract Health card 4

Formula

0 when gross savings ≤ 0 or the quality gate is not met otherwise round( gross savings × shared-savings rate × (1 − quality withhold), 2 )

Why this wayThe gate is binary: below it, savings generated earn nothing. That is why the demo shows about $20,570 generated and $0 settled. It is an estimate; the payer reconciles against run-out claims after the period closes.

In the demo$0.0 — Savings generated but the quality gate is not met (24.1% against a 60.0% minimum), so none of it is earned.

app/services/finance.py · contract_performance()

Open care gaps ('X of Y have no intervention started')

Contract Health card 5

Formula

Y = care gaps of ATTRIBUTED members whose status is an open state X = those still in OPEN or REOPENED (no work has moved them on)

Why this wayCounted over the contract's attributed members, so it can be lower than the Command Center's tenant-wide 'Open care gaps' (28 against 29 in the demo). 'No intervention started' is read from the gap's own state: a gap an intervention has touched has moved to in progress, scheduled or awaiting authorization.

In the demo28 open; 27 of 28 have no intervention started

app/services/vbc_manager.py · contract_health()

High / rising risk members

Contract Health card 6

Formula

attributed members whose risk tier is HIGH or RISING

Why this wayThe members most likely to drive next quarter's cost. Always 'monitor': a count of risk is not by itself good or bad. Tier bands are in Part I, Clinical risk score.

In the demo2 high, 4 rising, of 12 attributed

app/services/vbc_manager.py · contract_health()

Card status chips

Every Contract Health card

Formula

Actual PMPM: favourable when benchmark − actual ≥ 0, else unfavourable · Gross savings: positive when > 0, else attention · Quality score: on track when the gate is met, else at risk · Estimated settlement: positive when > 0, else at risk · Open care gaps: attention when any · High / rising: always monitor

Why this wayEach chip is a stated test on the card's own value, not a judgement typed by a person.

In the demoActual PMPM favorable, Gross savings positive, Quality score at_risk, Estimated settlement at_risk, Open care gaps attention, High / rising risk members monitor

app/services/vbc_manager.py · contract_health()

Path to the quality gate: Current, Required, Shortfall

Path to the quality gate panel

Formula

Current = the quality composite (same as card 3) Required = the contract's minimum quality % Shortfall = max( round(Required − Current, 1), 0 ) points

Why this wayThe distance to the only threshold that decides whether savings are paid. See Part I, Path to the quality gate.

In the democurrent 24.1%, required 60.0%, shortfall 35.9 pts

app/services/vbc_manager.py · gate_path()

Close these gaps first: Gaps (N of M), Rate after, Composite points

Path to the quality gate, plan table

Formula

measures in order of composite points per gap (highest first); from each take N = min( open gaps M, ⌈ remaining shortfall ÷ points per gap ⌉ ) until the gate is reached points per gap = 100 ÷ denominator ÷ rated measures, EXACT (not the 2-decimal figure shown) rate after = round( 100 × (numerator + N) ÷ denominator, 1 ) composite points = round( N × points per gap, 2 )

Why this wayGreedy is the fewest closures here because every gap in a measure is worth the same number of points. The arithmetic uses the exact per-gap value and rounds once at the end, so closing all six eye-exam gaps is worth +20.0 here and 20.0 on the Quality screen: one quantity, one number (a guard compares the two).

In the demoDiabetes: Eye Exam Performed: close 6 of 6 → 100.0%, +20.0, Diabetes: HbA1c Control (<8.0%): close 4 of 4 → 100.0%, +13.33, Statin Adherence (PDC >= 80%): close 2 of 6 → 50.0%, +5.0

app/services/vbc_manager.py · gate_path()

Projected if the plan runs, and 'N gap(s) would take the score to P%'

Path to the quality gate panel

Formula

projected = round( Current + Σ composite points of the plan, 1 ) N = Σ gaps in the plan

Why this wayWhat the arithmetic gives if exactly those gaps close and nothing else moves: a projection, not a forecast. 'Model this' on the Quality screen, run with the same plan, gives the same answer to within 0.1 point (the scenario engine re-computes each rate instead of adding points).

In the demo12 gaps → 62.4% against a 60.0% gate

app/services/vbc_manager.py · gate_path()

Trend: First and Latest

Trend table

Formula

each metric is captured as a dated point: measure rates, the quality composite, open gaps of attributed members, high/rising members, attributed members, PMPM First = the earliest point · Latest = today's point (re-captured on every read; one point per day, the day's last value wins)

Why this wayA trend is only as honest as its points, so today's is captured from the live engines rather than typed. The early points in the demo are a seeded synthetic backfill and the screen says so.

In the demoAttributed members 12.0 → 12.0, PMPM (allowed) 1638.32 → 1554.32, Open care gaps 36.0 → 28.0, Annual Wellness Visit 9.6 → 22.2, Controlling High Blood Pressure (<140 systolic) 27.4 → 40.0, Diabetes: HbA1c Control (<8.0%) 20.7 → 33.3

app/services/trends.py · capture()app/services/trends.py · catalogue()

Trend: Change and Direction (improving / deteriorating / flat)

Trend table

Formula

change = round( latest − first, 2 ) improving when change > 0 for a metric where up is good (rates, composite, attributed members), or change < 0 where down is good (open gaps, high/rising risk, PMPM); flat when change = 0

Why this wayDirection is judged against each metric's own sense of good: open gaps falling is improvement. Colouring every rise green would teach people to stop reading the column.

In the demoAttributed members flat, PMPM (allowed) improving, Open care gaps improving, Annual Wellness Visit improving, Controlling High Blood Pressure (<140 systolic) improving, Diabetes: HbA1c Control (<8.0%) improving, Diabetes: Eye Exam Performed flat, Statin Adherence (PDC >= 80%) improving, Quality composite improving, High or rising risk members improving

app/services/trends.py · catalogue()

Trend: 'N point(s) over D day(s)'

Trend table, History column

Formula

N = the number of captured points · D = days between the first and the latest point

Why this wayShown so a reader can tell a trend from a single reading: a metric with one point has no trend, and the projection engine refuses to project it (Part I, Trends and projection).

In the demoAttributed members 1 over 0 d, PMPM (allowed) 7 over 42 d, Open care gaps 7 over 42 d, Annual Wellness Visit 7 over 42 d, Controlling High Blood Pressure (<140 systolic) 7 over 42 d, Diabetes: HbA1c Control (<8.0%) 7 over 42 d, Diabetes: Eye Exam Performed 7 over 42 d, Statin Adherence (PDC >= 80%) 7 over 42 d, Quality composite 7 over 42 d, High or rising risk members 7 over 42 d

app/services/trends.py · catalogue()

Today's priorities (count, order, why, due)

Today's priorities panel

Formula

a row is raised when: the quality gate is not met (severity 100, due the contract end) · a discharge is 3–7 days old (90, due today) · up to 2 cost categories over benchmark (80) · a campaign active ≥ 3 days with every task still open (70) · up to 2 measures below target (60 + that measure's composite points available) rows sorted by severity, highest first

Why this waySeverity is a fixed, published scale rather than a hidden score: the gate outranks everything because below it no savings are paid at all. Each row's 'why' quotes the numbers that raised it.

In the demoQuality gate is not met (100), Inpatient over benchmark (80), Pharmacy over benchmark (80), Diabetes: Eye Exam Performed below target (80.0), Annual Wellness Visit below target (75.56)

app/services/vbc_manager.py · priorities()app/services/vbc_manager.py · _stalled_campaigns()

Finance

24 values

The VBC manager's and executive's money view: cost per member per month and what drives it, utilisation, the contract's settlement arithmetic, the outcomes the contract is judged on, and observed clinical change.

Attributed members

Cost KPI cards

Formula

members attributed in the current attribution period that contains today (no open period: every member in the tenant)

Why this wayCost is only meaningful over the population a contract holds you responsible for, so every cost figure on this screen divides by this population's member-months.

In the demo12 members

app/services/finance.py · cost_summary()app/services/population.py · attributed_patients()

Member months

Cost KPI cards; also behind every PMPM

Formula

Σ over the period's attributions of months between max(attributed date, period start) and min(period end, today) months between = whole months + remaining days ÷ 30.44, rounded to 2 decimals (no open period: members × months elapsed since 1 January; no members, no member-months)

Why this wayIt counts months ELAPSED to date, so a cost-to-date is divided by the months it was incurred over, not by the year. A part month counts as its share (30.44 = the average days in a month). Known inconsistency: this period total does not stop a member at an attribution end date, while the monthly PMPM chart below does.

In the demo105.12 member-months

app/services/finance.py · member_months()app/services/finance.py · months_between()

Allowed, Paid, Member liability

Cost KPI cards

Formula

sums of allowed, paid and member_liability over claims of attributed members with a service date between the period start and min(period end, today)

Why this wayAllowed = what the plan recognises for the service (paid + member liability). Cost performance is judged on allowed, because that is what the benchmark is set on; paid alone would reward shifting cost to the member.

In the demoallowed $163390.0 = paid $129649.6 + member liability $33740.4

app/services/finance.py · cost_summary()

PMPM allowed (actual PMPM)

Cost KPI cards; Contract card; Contract Health

Formula

PMPM = round( allowed ÷ member months, 2 ); blank when member months is 0

Why this wayThe single cost figure the contract is scored on, computed in one place and read by Finance, Contract Health and the trend. See Part I, Contract performance and settlement.

In the demo$1554.32 = 163390.0 ÷ 105.12

app/services/finance.py · cost_summary()

ED visits and Inpatient admissions (count and per 1,000/yr)

Cost panel, utilisation lines

Formula

n = encounters of that kind for attributed members, admitted in the period per 1,000/yr = round( n ÷ (member months ÷ 12) × 1000, 1 )

Why this wayPer 1,000 members per year is the unit payer benchmarks are published in, so the figure can be compared with a benchmark without conversion. The same two figures appear in the Utilisation panel, computed by a second function with the same rule.

In the demoED visits: 3 → 342.5 per 1,000/yr, Inpatient admissions: 4 → 456.6 per 1,000/yr

app/services/finance.py · cost_summary()

Contract card: Benchmark PMPM

Contracts panel

Formula

the contract's benchmark PMPM (a stored contract term, not computed)

Why this wayThe number actual cost is measured against. It is a negotiated term, so it is read from the contract, never derived.

In the demo1750 $ PMPM

app/services/finance.py · contract_performance()

Contract card: Actual PMPM and Variance PMPM

Contracts panel

Formula

actual = the period PMPM above variance = round( benchmark − actual, 2 ); positive = under benchmark (good)

Why this waySign convention chosen so a positive variance is money saved. Known limit: every active contract card shows the tenant's current-period PMPM; the contract's own period is printed but does not narrow the cost.

In the demo1750.0 − 1554.32 = 195.68

app/services/finance.py · contract_performance()

Contract card: Gross savings

Contracts panel

Formula

gross savings = round( variance PMPM × member months, 2 ); negative when over benchmark

Why this wayThe variance is the displayed, cent-rounded figure, so the product can be checked on a calculator from the card itself.

In the demo195.68 × 105.12 = $20569.88

app/services/finance.py · contract_performance()

Contract card: Quality score and 'Quality gate met / NOT met'

Contracts panel

Formula

quality score = the quality composite (Part I, The quality composite) gate met = composite exists and composite ≥ the contract's minimum quality %

Why this wayRead from the one composite function every screen uses; the minimum is the contract's term.

In the demo24.1% against 60.0% → NOT met

app/services/finance.py · contract_performance()app/services/quality.py · composite()

Contract card: Est. settlement

Contracts panel

Formula

0 when gross savings ≤ 0, or when the quality gate is not met otherwise round( gross savings × shared-savings rate × (1 − quality withhold), 2 )

Why this wayThe gate is binary: savings below it are generated but not earned. Labelled an estimate, because the payer reconciles against run-out claims after the period.

In the demo$0.0 — Savings generated but the quality gate is not met (24.1% against a 60.0% minimum), so none of it is earned.

app/services/finance.py · contract_performance()

Cost drivers: claims, category PMPM, share %

What is driving PMPM panel

Formula

per place of service: claims = count; allowed = Σ allowed category PMPM = round( category allowed ÷ TOTAL period member months, 2 ) share % = round( 100 × category allowed ÷ total allowed, 1 )

Why this wayEvery category divides by the same member-months, so the category PMPMs add up to the total PMPM: a driver is a slice of the one number, not a separate average.

In the demoProfessional / office: 60 claims, $556.74 PMPM, 35.8%, Inpatient: 4 claims, $540.33 PMPM, 34.8%, Pharmacy: 48 claims, $395.89 PMPM, 25.5%, Emergency department: 3 claims, $61.36 PMPM, 3.9%, Outpatient: 0 claims, $0.0 PMPM, 0.0%

Check it yourselfThe category PMPMs add up to the total PMPM on the Cost cards (to the cent, allowing for rounding).

app/services/finance.py · cost_drivers()

Cost drivers: Benchmark and Variance (over / under)

What is driving PMPM panel

Formula

benchmark = the first active contract's benchmark for that category ('not set' when there is none) variance = round( benchmark − category PMPM, 2 ); red (over benchmark) when below 0

Why this waySame sign convention as the contract: positive is under benchmark. A category with no contracted benchmark is not judged at all rather than compared with an invented one.

In the demoProfessional / office +143.26, Inpatient -0.33, Pharmacy -15.89, Emergency department +28.64, Outpatient +40.0

app/services/finance.py · cost_drivers()

Cost drivers: Month over month

What is driving PMPM panel

Formula

for the last two calendar months: each month's category allowed ÷ that month's member months change = round( current month − prior month, 2 ); the current month is partial and says so

Why this wayA month-to-date figure read as a full month nearly always looks like an improvement, so the partial month is flagged every time.

In the demoProfessional / office 31.14 → 20.29 (-10.85), Inpatient 0.0 → 1557.02 (+1557.02), Pharmacy 0.0 → 0.0 (+0.00), Emergency department 0.0 → 235.75 (+235.75), Outpatient 0.0 → 0.0 (+0.00)

app/services/finance.py · cost_drivers()app/services/finance.py · member_months_window()

Cost drivers: Members (per category)

What is driving PMPM panel

Formula

distinct members among the category's claims

Why this wayTells a reader whether a category is a few expensive members or a broad pattern, which calls for a different action.

In the demoProfessional / office 12, Inpatient 3, Pharmacy 12, Emergency department 3, Outpatient 0

app/services/finance.py · cost_drivers()

Cost drivers: headline sentence and small-population note

What is driving PMPM panel

Formula

if any category is over its benchmark: name the one furthest over and its $ PMPM over otherwise: name the largest category by allowed dollars and its share the volatility note appears when attributed members < 100

Why this wayOne sentence answering 'what should I look at first'. Under 100 members a single admission moves PMPM visibly, so the screen warns before anyone reads a trend into it.

In the demoPharmacy is $15.89 PMPM over its benchmark — the largest cost opportunity in the contract.

app/services/finance.py · cost_drivers()

Utilisation: Inpatient admissions, ED visits, Ambulatory / PCP visits per 1,000/yr

Utilisation panel

Formula

n = encounters of attributed members admitted in the period (inpatient; emergency; ambulatory + virtual) per 1,000/yr = round( n ÷ (member months ÷ 12) × 1000, 1 )

Why this wayThe payer-standard unit. Ambulatory counts virtual visits too, because a telehealth primary-care visit is the utilisation a value-based contract wants more of.

In the demoInpatient admissions 456.6 (n=4), ED visits 342.5 (n=3), Ambulatory / PCP visits 1141.6 (n=10)

app/services/finance.py · utilization()

Utilisation: Average length of stay

Utilisation panel

Formula

round( Σ (discharge date − admission date) days ÷ number of stays, 1 ) over attributed members' inpatient stays in the period that have ended

Why this wayStays still in progress are left out, because a stay counted before it ends would make length of stay look shorter than it is.

In the demo4 days

app/services/finance.py · utilization()

30-day readmission rate (Utilisation panel and Patient outcomes)

Utilisation panel; Patient outcomes cards

Formula

index discharges = inpatient discharges whose 30-day window has fully passed readmissions = index discharges followed by another inpatient admission 1–30 days later (at most one each) rate = round( 100 × readmissions ÷ index, 1 ); not available with no index discharge 'window still open' = discharges less than 30 days ago, excluded

Why this wayA discharge whose window has not closed is excluded rather than counted as 'not readmitted', which would flatter the rate. Known inconsistency: this rate is computed over every member in the tenant, while the other utilisation rows use attributed members in the period.

In the demo33.3% = 1 of 3 index discharges; 1 still inside the window

app/services/outcomes.py · readmission_rate()

Post-discharge follow-up within 7 days

Utilisation panel

Formula

index = inpatient discharges at least 7 days ago followed = those with an ambulatory or virtual visit 1–7 days after discharge rate = round( 100 × followed ÷ index, 1 ); not available with no index discharge

Why this wayThe same windowing rule as readmissions: only discharges whose 7 days have passed are judged. Like readmissions it is computed over every member in the tenant.

In the demo0.0%

app/services/finance.py · post_discharge_followup()

Specialist visits and Avoidable ED ('NOT AVAILABLE')

Utilisation panel

Formula

not computed; shown with the reason it cannot be

Why this wayThe synthetic data has no specialty on encounters and no avoidable-ED classification (such as the NYU algorithm). A metric that cannot be measured says so in place instead of showing a zero, because a zero is an answer.

In the demoSpecialist visits, Avoidable ED utilisation

app/services/finance.py · utilization()

PMPM by month (member months, allowed, PMPM, 'partial')

PMPM by month panel

Formula

for each of the last 6 calendar months: member months = Σ over attributions clipped to the month (and to any attribution end date); allowed = that month's claims PMPM = round( allowed ÷ member months, 2 ); partial when the month has not ended

Why this wayEach month is divided by its own member-months, so months are comparable. The current month is flagged partial because its PMPM is month-to-date.

In the demo2026-04 $2158.25, 2026-05 $3271.46, 2026-06 $1488.25, 2026-07 $1103.45, 2026-08 $31.14, 2026-09 $1813.05 (partial)

app/services/finance.py · pmpm_by_month()app/services/finance.py · member_months_window()

Outcomes the contract is judged on (quality, gap closure, ED per 1,000, PMPM, readmissions, clinical improvement)

Outcomes panel

Formula

quality = the quality composite · gap closure = round( 100 × Σ numerators ÷ Σ denominators over rated measures, 1 ) (pooled compliance) · ED per 1,000 and PMPM = the cost summary's figures · readmissions = the 30-day rate · clinical improvement = round( 100 × members improved ÷ members evaluated, 1 ) across the tracked analytes

Why this wayEach row re-uses the engine that owns the number, so this table cannot disagree with the panels above it. A value the engine cannot compute yet (for example, no discharge whose 30-day window has closed) shows as not available with the reason, never as 0.0, and is not frozen into the trend: zero is a score somebody earned.

In the demoImprove quality (composite measure rate) 24.1 %, Close care gaps 25.6 %, Reduce avoidable utilization (ED visits) 342.5 per 1,000/yr, Lower total cost of care 1554.32 $ PMPM, Reduce avoidable utilization (30-day readmissions) 33.3 %, Patient outcomes (members whose tracked values improved) 71.4 %

app/services/finance.py · compute_outcomes()

Member experience and 'Succeed in value-based contracts' (NOT MEASURED)

Outcomes panel

Formula

not computed; a NOT MEASURED badge

Why this wayThere is no survey (CAHPS) or grievance source in the platform, so these are declared unmeasured rather than estimated.

In the demoImprove member experience, Succeed in value-based contracts

app/services/finance.py · compute_outcomes()

Patient outcomes: evaluated, improved (%), worsened, mean change, at target, not evaluable

Patient outcomes table

Formula

four tracked results, all lower-is-better: HbA1c (target 8.0%), systolic BP (140 mmHg), total cholesterol (200 mg/dL), creatinine (1.3 mg/dL) per member: change = latest − earliest reading on file; improved when change < 0, worsened when > 0; not evaluable with only one reading improved % = round( 100 × improved ÷ evaluated, 1 ); mean change = round( mean of changes, 2 ); at target = latest ≤ target

Why this wayAn observed change between readings, not a claim that any intervention caused it. Computed over every member with readings, with no attribution or date window, and a member with a single reading is counted as not evaluable instead of unchanged.

In the demoHbA1c: 3 of 5 improved (60.0%), mean -0.24 %, Systolic BP: 4 of 5 improved (80.0%), mean -6.4 mmHg, Total cholesterol: 2 of 3 improved (66.7%), mean +1 mg/dL, Creatinine: 1 of 1 improved (100.0%), mean -0.1 mg/dL

app/services/outcomes.py · population_change()app/services/outcomes.py · patient_trajectory()

Campaigns, actions and outcomes

52 values

The VBC manager's (and the executive's) Actions & Outcomes screen: the operational KPI board, population-level next best actions, how providers are answering the opportunities sent to them, how each campaign's members are progressing, and what the care team has delivered.

Operational KPIs: 'N of M measurable here'

Operational KPIs panel, header

Formula

M = every KPI the business case names (16, in four groups) N = those with a value; a card with no value reads 'not measured here' and states why

Why this waySix KPIs (average case resolution, first contact resolution, CAHPS, grievances, member satisfaction, research time) have no source in this platform and are always shown with the system that owns them, never as 0 and never dropped. A rate over an empty denominator also shows as 'not measured here', so N can move as data arrives. The panel shows no targets by design: none has been agreed.

In the demo10 of 16

Check it yourselfCount the cards that show a number: they equal N; the rest say 'not measured here'.

app/services/opskpi.py · board()app/static/app.js · opsKpiPanel()

Referral completion (%, 'over N')

Operational KPIs, Operational group

Formula

numerator = internal referrals at COMPLETED or OUTCOME_CONFIRMED + vendor referrals at CONFIRMED N = settled referrals: internal OUTCOME_CONFIRMED, DECLINED, CANCELLED, UNABLE_TO_CONTACT + vendor CONFIRMED, DECLINED, FAILED, CANCELLED rate = round( 100 × numerator ÷ N, 1 ); 'not measured here' when N = 0

Why this wayIn-flight referrals are left out of the denominator rather than counted as failures. Known inconsistency: internal COMPLETED ('team reports it done') is counted as a success here, but it is not a settled state, so it is in the numerator without being in N; the rate can therefore exceed 100%. The per-person 'Referrals (loop closed)' column further down this same screen counts only OUTCOME_CONFIRMED, so the two can differ. Tenant-wide, all time. See Part I, Referral SLA, for what settles a referral.

In the demo100% over 1

Check it yourselfIn the demo the one settled referral is a confirmed vendor referral: 1 ÷ 1 = 100%.

app/services/opskpi.py · board()

Cross-functional handoffs

Operational KPIs, Operational group

Formula

count of internal referrals in the tenant, any status, all time

Why this wayA count, so 0 is shown as 0 (a measurement), not as 'not measured'. Vendor referrals are not counted. The card also prints 'over N' where N is the same count, which adds nothing.

In the demo0 handoffs

app/services/opskpi.py · board()

Case backlog (mirrored)

Operational KPIs, Operational group

Formula

count of mirrored service cases with no closed_on date; 'over N' = all mirrored cases 'not measured here' when no case has been mirrored at all

Why this waySalesforce holds the real backlog; this counts only the cases this platform has been told about, and the card says so. With nothing mirrored the card refuses rather than printing 0, because 'no view' is not 'empty'.

In the demo1 over 1

app/services/opskpi.py · board()

Complaints (mirrored)

Operational KPIs, Member experience group

Formula

count of mirrored service cases whose case_type, lower-cased, is complaint; 'over N' = all mirrored cases 'not measured here' when no case has been mirrored

Why this waySame limit as the backlog: only complaints mirrored from Salesforce. CAHPS, grievances and member satisfaction beside it are never computed here and say which system owns them.

In the demo1 over 1

app/services/opskpi.py · board()

Outreach response (%, 'over N')

Operational KPIs, Engagement group

Formula

N = outreach ladders with at least one attempt numerator = ladders with any recorded outcome whose status is not PLANNED rate = round( 100 × numerator ÷ N, 1 )

Why this wayCounted per ladder, not per message. Known limit: ANY recorded outcome sets the field this counts, including 'No answer', 'Voicemail left' and 'Wrong number', so a ladder that never reached the member can count as a response; and the numerator is not restricted to attempted ladders, so the rate is not bounded by 100%. The care-team 'Response' column on this screen is a different figure (members with an inbound message, per person).

In the demo100% over 2

Check it yourselfIn the demo both attempted ladders are at REACHED (needs appointment; transportation barrier): 2 ÷ 2.

app/services/opskpi.py · board()app/services/engagement.py · record_outcome()

HRA completion (%, 'over N')

Operational KPIs, Engagement group

Formula

round( 100 × HRAs at COMPLETED ÷ every HRA raised (any status), 1 )

Why this wayThe denominator is every assessment raised, so an HRA nobody chased still counts against completion; the plan cannot choose a friendlier denominator.

In the demo35.7% over 14

app/services/opskpi.py · board()

Care-management enrolment (%, 'over N')

Operational KPIs, Engagement group

Formula

N = programme enrolments at OFFERED, ACCEPTED, ENROLLED or DECLINED rate = round( 100 × those at ENROLLED ÷ N, 1 )

Why this wayEnrolled over everyone offered, not over enrolments, so declines count against it. Known limit: an enrolment that later moved to DISENROLLED (or is still ELIGIBLE) is in neither count, so a member who enrolled and left drops out of the denominator.

In the demo66.7% over 12

app/services/opskpi.py · board()

Members with any engagement (%, 'over N')

Operational KPIs, Engagement group

Formula

N = every member in the tenant numerator = distinct members with any engagement message (either direction) or an outreach ladder with at least one attempt rate = round( 100 × numerator ÷ N, 1 )

Why this wayThe card's basis reads 'members this platform has attempted to reach', but an inbound-only message also counts, so it is 'any two-way contact recorded'. N is the whole tenant, not the attributed population.

In the demo15.4% over 13

app/services/opskpi.py · board()

Duplicate outreach prevented

Operational KPIs, Productivity & coordination group

Formula

number of refused-duplicate and coordination-gate audit events in the last 30 days (measured back from the current time), excluding routine sweep idempotency

Why this wayA duplicate that never happens looks like nothing happening unless it is counted. The same function feeds the Duplicate activity prevented screen. No denominator is shown: there is no meaningful 'out of'.

In the demo0 prevented

app/services/prevention.py · summary()app/services/opskpi.py · board()

Completed work per coordinator ('over N')

Operational KPIs, Productivity & coordination group

Formula

N = distinct people who own at least one task (any status) value = round( tasks at done in the tenant ÷ N, 1 )

Why this wayA workload figure, not a score, and not ranked per person. Known limit: the numerator counts every done task in the tenant, including tasks with no owner, while N counts only owners, so unowned completed work inflates the per-person figure. All time.

In the demo3 over 1

Check it yourselfDemo: 3 done tasks, all owned by Jane Smith, 1 owner: 3 ÷ 1 = 3.0.

app/services/opskpi.py · board()

Population next best actions (count)

Population next best actions panel, title count

Formula

number of recommendations after sorting by score (highest first) and keeping the first 8

Why this wayAt most 4 quality pushes plus one each for transitions, adherence, cost and enrolment can be raised, which is 8, so the limit never drops a row today. The whole panel is advisory: nothing is created until a person launches it.

In the demo8 recommendations

app/services/vbc_manager.py · population_actions()

Order of the recommendations

Population next best actions table, row order

Formula

sorted by a hidden score, highest first (ties keep this order): quality push = composite points available × 10 · post-discharge = 60 + discharges due · adherence = 55 · cost = 50 + |PMPM over benchmark| ÷ 10 · enrolment = 48

Why this wayThe score is an ordering rule, not a measurement, and it mixes units (composite points against dollars), so the exchange rate between them is a policy choice. A cost row needs more than $50 PMPM over benchmark to outrank adherence, and more than $100 plus $10 per discharge due to outrank the post-discharge queue; a quality push outranks that queue once it is worth more than 6 composite points (plus 0.1 per discharge due). In the demo all four quality pushes (13.33 to 20 points) rank first. The score is not shown.

In the demoquality_push 200.0, quality_push 155.6, quality_push 150.0, quality_push 133.3, transitions 61.0, adherence 55.0, cost 51.589, enrollment 48.0

app/services/vbc_manager.py · population_actions()

Quality push rows: Members and 'N open gaps, P points below target — worth X composite points'

Population next best actions table, 'Prioritise … outreach' rows

Formula

the first 4 measures in the Quality opportunity order (points available, largest first), skipping any with 0 open gaps Members = open gaps = D − N · points below target = round( target − rate, 1 ), shown only when above 0 · worth = composite points available

Why this wayOnly the top 4 are offered, so a measure ranked fifth (in the demo, blood pressure control) is not proposed even with open gaps. Open gaps equal members because a member has at most one open gap per measure. Every figure is the Quality screen's own; see Part I, Quality opportunity and points per gap.

In the demoEED-RETINAL: 6 members, AWV: 7 members, SUPD-STATIN: 6 members, CDC-HBA1C: 4 members

Check it yourselfThe same four measures lead the Quality screen with the same open gaps and composite points; 'Open' lists exactly that many members.

app/services/vbc_manager.py · population_actions()app/services/vbc_manager.py · quality_opportunity()

Post-discharge follow-up row: Members

Population next best actions table, 'Work the post-discharge follow-up queue'

Formula

number of inpatient discharges 0 to 120 days old that are still inside the 7-day visit window (today − discharge date ≤ 7)

Why this wayThe window closes whether or not anyone acts, so it is the one event-driven row. It counts DISCHARGES, not distinct members, though the column is headed Members. Known defect: the row's Open button loads the Transitions payload, which the drill modal does not recognise, so it shows 'Nothing to show' over a non-zero count. See Part I, Transition-of-care windows.

In the demo1 discharges

Check it yourselfThe Transitions screen's 'Visit window open' card shows the same number.

app/services/vbc_manager.py · population_actions()app/services/transitions.py · recent_discharges()

Medication adherence row: Members ('N member(s) below the 80% PDC threshold')

Population next best actions table, 'Assign outreach for medication adherence'

Formula

distinct members with an active medication whose PDC is below 0.80 (a missing PDC counts as adherent)

Why this wayCounts the whole tenant, not only attributed members. Known defects: the test is written (pdc or 1.0) < 0.80, so a PDC of exactly 0 (never filled) is read as 1.0 and that member is NOT counted, while the member's own risk and attention panels do count them; and the Open button lists the top 10 members by cost × actionability, not these members.

In the demo3 members

app/services/vbc_manager.py · population_actions()

Cost row: '$X PMPM over benchmark across N member(s)'

Population next best actions table, 'Investigate … utilisation'

Formula

among cost categories whose variance (benchmark − category PMPM) is below 0, the most negative $X = |variance|, 2 decimals · Members = distinct attributed members with a claim in that category in the period

Why this wayA financial action, not a clinical one, and raised only when the contract sets a benchmark for the category; with none there is nothing to be over. The figures are the Financial screen's cost-driver row for that category.

In the demo$15.89 PMPM over benchmark across 12 member(s)

Check it yourselfFinancial → Cost drivers: the same category shows the same variance and Members; 'Open' lists that many members.

app/services/vbc_manager.py · population_actions()app/services/finance.py · cost_drivers()

Cost row 'Open': Claims and Allowed per member

Drill modal behind the cost row

Formula

per attributed member with a claim in the category, service date from the period start to min(period end, today): Claims = count · Allowed = Σ allowed, rounded to cents as it is added rows sorted by Allowed, largest first

Why this wayThe members behind the dollar figure. The row count equals the recommendation's Members. The allowed total is summed per member and rounded as it goes, while the category row rounds once at the end, so the two can differ by a cent if a claim amount carries sub-cent precision.

In the demo12 members; top: John Doe, 4 claims, $6290.00

app/services/finance.py · category_members()

Enrolment row: Members ('N high-risk member(s) are in no programme')

Population next best actions table, 'Offer care management to unenrolled high-risk members'

Formula

number of 'offer enrolment' items among the member-level next best actions, computed with a limit of 50

Why this wayA high-risk member outside every programme is surfaced by no standing measure, so it is raised here. Known limits: the member-level list is cut to 50 ranked items first, so in a larger population this can undercount; dismissed recommendations are not counted; and the Open button lists the top 10 members by cost × actionability, not these members. See Part I, Next best action.

In the demo1 members

app/services/vbc_manager.py · population_actions()app/services/nba.py · recommend()

Quality push 'Open': the member list (count, Risk, Barrier, In flight, Recommended action)

Drill modal behind each quality push row (also behind 'Members' on the Quality screen)

Formula

one row per live care gap on the measure (statuses OPEN, IN_PROGRESS, SCHEDULED, AWAITING_AUTH, SERVICE_COMPLETED, REOPENED) sorted HIGH, RISING, MEDIUM, LOW, then name · In flight = the gap's first intervention, else 'nothing started' · Recommended action = that gap's member-level next best action

Why this wayThe recommendation is the same one the member's care manager sees, so the two screens cannot disagree. Barrier is the measure engine's own reason the gap is open. The list is every tenant member with a live gap, not only attributed members. The modal's 'auth <ref>' branch can never show: a blocking authorisation is only looked up when an intervention exists, and then the intervention's status is shown instead.

In the demoEED-RETINAL: 6 members, first John Doe (HIGH)

Check it yourselfThe number of rows equals the row's Members (6 for the eye exam in the demo).

app/services/vbc_manager.py · measure_members()app/static/app.js · drillBody()

Provider responses (count)

Provider responses panel, title count

Formula

count of every provider opportunity in the tenant, any status (including closed and withdrawn), all time

Why this wayIt is the sum of the Sent column. The panel is hidden entirely when no opportunity has been sent.

In the demo2 opportunities

Check it yourselfAdd up the Sent column.

app/services/opportunities.py · summary()

Sent (per provider)

Provider responses table, Sent column

Formula

count of the provider's opportunities, any status, all time

Why this wayThe denominator for evidence returned (not shown on this screen). Settled and withdrawn opportunities stay in it. See Part I, Provider response metrics.

In the demoDr. P. Osei 2

Check it yourself'view' on the row lists exactly this many opportunities.

app/services/opportunities.py · summary()

Awaiting (per provider)

Provider responses table, Awaiting column

Formula

count of the provider's opportunities at NEW, VIEWED or MORE_INFO_NEEDED

Why this wayWork waiting on the provider. A 'more information needed' answer is both awaiting AND responded, so Sent − Awaiting is not the number of responses and the columns do not add up to Sent.

In the demoDr. P. Osei 2

app/services/opportunities.py · summary()

Accepted, Disputed (per provider)

Provider responses table, Accepted and Disputed columns

Formula

count of the provider's opportunities whose recorded response is ACCEPTED / DISPUTED, whatever their status now

Why this wayThe recorded answer, not the current status, so an accepted opportunity later withdrawn still counts as accepted. This screen shows the counts; the disputed RATE and evidence-returned rate in Part I, Provider response metrics are computed but not shown here.

In the demoDr. P. Osei 0 accepted, 0 disputed

app/services/opportunities.py · summary()

Acceptance (%)

Provider responses table, Acceptance column

Formula

responded = the provider's opportunities with a response date (any of the five answers, including 'more information needed') acceptance = round( 100 × accepted ÷ responded ), a whole number; 'no responses yet' when responded = 0

Why this wayJudged only against answers: silence is not a refusal. See Part I, Provider response metrics. The responded count is not on screen, so the % cannot be rebuilt from the visible columns. Rounding is half-to-even (1 of 8 = 12.5 shows 12). Part I says the empty case 'reads as a dash'; the screen actually prints the words 'no responses yet'.

In the demoDr. P. Osei no responses yet

app/services/opportunities.py · summary()

Median days

Provider responses table, Median days column

Formula

response time = response date − the business date the opportunity was created, whole days sorted; value = the element at position ⌊n ÷ 2⌋ (counting from 0); '—' when nothing has been answered

Why this wayFor an odd count this is the true median. For an even count it is the UPPER of the two middle values, not their average: response times 1 and 3 show 3, where the median is 2. Part I, Provider response metrics calls it 'the middle value'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfOnce a provider answers, 'view' lists each opportunity; the business-day gap between creation and response gives the list to sort.

app/services/opportunities.py · summary()app/services/opportunities.py · business_date_of()

Overdue (column, 'N overdue' title badge, OVERDUE badge and Due in the drill)

Provider responses table and title; 'view' drill modal

Formula

Due = business date at creation + response days (tenant setting provider.response_days, default 7), fixed at creation overdue = Due < today AND status not settled (CLOSED, WITHDRAWN, DISPUTED, NOT_CLINICALLY_APPROPRIATE) rows sorted by Overdue, then Sent, highest first; the title badge is the tenant total, hidden at 0

Why this wayThe due day itself is not overdue. The due date never moves on a response, so an accepted (in progress), 'more information needed' or evidence-returned opportunity still turns overdue: the badge means 'not settled by the response deadline', not 'not answered'. The rule that raises a chase task for an unanswered opportunity (NEW or VIEWED only) fires ON the due day with its default grace of 0, one day before this badge appears.

In the demo0 overdue

Check it yourselfIn the demo both of Dr. P. Osei's opportunities were created on the reset day and fall due 7 days later, so none is overdue; 'view' shows the Due date on each.

app/services/opportunities.py · row()app/services/opportunities.py · response_days()app/services/opportunities.py · summary()app/services/task_rules.py · _r_opportunity_unanswered()

Intervention performance (campaign count)

Intervention performance panel, title count

Formula

count of every campaign in the tenant, active or closed, newest first

Why this wayRe-running a push tops up the live campaign for that target instead of creating a second one, so this counts distinct pushes. With none the panel says 'No campaigns yet'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfLaunch a push from a quality row (or the Quality screen); the count goes to 1, and launching the same measure again leaves it at 1.

app/services/campaigns.py · performance()

Eligible

Intervention performance table; funnel stage 'Eligible population'

Formula

number of members stored on the campaign: at first launch, members with a live gap on the measure (evaluated then) or the cohort's members; each re-run appends newly eligible members and removes nobody

Why this wayStored rather than recomputed, so the denominator cannot drift under the result. The cost: a member whose gap has since closed stays in it, and the denominator grows when a manager re-runs the push.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · launch()app/services/campaigns.py · funnel()

Work generated

Intervention performance table; funnel stage 'Work generated'

Formula

number of tasks carrying this campaign's id, any status

Why this wayA TASK count, not a member count. Members already holding this campaign's open work are skipped, and a member folded into another campaign's open task gets no new task, so it can be below Eligible; a re-run after a task is done can add a second task for the same member, so it can also exceed it. The funnel heads the column 'Members' regardless.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Started

Intervention performance table; funnel stage 'Intervention created'

Formula

distinct eligible members with ANY intervention in the tenant (any status, any measure, created before or after launch)

Why this wayA campaign creates tasks, never interventions, so there is no link to count by. The cost: an intervention that pre-dates the campaign, or is for another measure, or was cancelled, still counts as started.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Reached

Intervention performance table; funnel stage 'Member reached'

Formula

distinct eligible members with an intervention at ENGAGED or later (AWAITING_AUTH, AUTH_APPROVED, SCHEDULED, IN_PROGRESS, COMPLETED, GAP_CLOSED), or with any inbound message

Why this wayA sent message is not a reached member; a reply or an engaged intervention is. Unreachable, declined and cancelled interventions never count. No date or purpose filter, so a reply from before the launch counts.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()app/services/campaigns.py · _reached_at_least()

Completed

Intervention performance table; funnel stage 'Service completed'

Formula

distinct eligible members with an intervention at COMPLETED or GAP_CLOSED

Why this wayAttendance, not authorisation: the service happened. Any measure, any date.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Gaps closed

Intervention performance table; funnel stage 'Gap closed'

Formula

distinct eligible members with a care gap at CLOSED on the campaign's measure (any measure for a cohort campaign)

Why this wayOnly the measure engine closes a gap, on clinical evidence. There is no closed-date filter, so a closure from before the launch also counts, and the screen's own caveat applies: observed progression, not attributed effect.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Measure rate ('B% → C%', '±N pts')

Intervention performance table, Measure rate column

Formula

B = the measure's rate when the campaign was FIRST launched (never moved by a re-run) C = the same measure's rate now · change = round( C − B, 1 ) points '—' for a cohort campaign

Why this wayBoth rates are the Quality screen's rate (see Part I, A measure across a population) over the whole population, so the change is a before-and-after, not a campaign effect; the screen says so under the table.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfC equals the measure's Performance on the Quality screen.

app/services/campaigns.py · launch()app/services/campaigns.py · performance()app/services/quality.py · evaluate_tenant()

Funnel: Outreach attempted and Appointment scheduled (stage counts)

Campaign funnel modal ('Funnel' button), Members column

Formula

Outreach attempted = distinct eligible members with an intervention at OUTREACH or later, or any outbound message Appointment scheduled = those at SCHEDULED or later, or with an appointment BOOKED, CONFIRMED, ARRIVED or COMPLETED (the other six stages are the table columns above)

Why this wayEight stages from eligible to closed so the drop between them is visible; the drop is the finding. Like every stage, these read all of the eligible members' records in the tenant with no launch-date bound. Cancelled and no-show appointments do not count as scheduled.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfLaunch a push and click 'Funnel': eight stages, each with its count; Eligible, Work generated, Started, Reached, Completed and Gaps closed equal the table row.

app/services/campaigns.py · funnel()

Funnel: % of eligible

Campaign funnel modal, % of eligible column

Formula

round( 100 × stage count ÷ Eligible, 1 ); '—' when Eligible is 0

Why this wayEvery stage against the stored population. Work generated is a task count, so its percentage can exceed 100%.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Funnel: % of previous

Campaign funnel modal, % of previous column

Formula

round( 100 × stage count ÷ previous stage count, 1 ) when the previous stage is 0 (and for the first stage): 100.0 if this stage is above 0, else '—'

Why this wayShows where members stop moving. Two quirks: a stage that follows an empty one reads 100.0% (0 completed, then 2 gaps closed shows '100.0%'); and the step from Work generated to Intervention created divides members by tasks.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · funnel()

Funnel: 'Where it leaks' (N member(s) do not progress from A to B)

Campaign funnel modal, note under the table

Formula

the consecutive pair of stages with the largest drop (count A − count B); on a tie the earlier pair; no note when no stage drops

Why this wayThe one sentence a manager acts on. The note always says 'member(s)', but the two steps touching Work generated compare members with tasks, so a leak reported there is not a member count.

In the demo— nothing to show in the fresh demo tenant

app/services/campaigns.py · _biggest_drop()

Funnel: 'Stopped: N member(s) ended unreachable, declined or cancelled'

Campaign funnel modal, note under the table

Formula

number of the eligible members' interventions at UNREACHABLE, DECLINED or CANCELLED

Why this wayCounted apart from 'not yet reached' because a channel that is not working should not hide inside a queue. It counts INTERVENTIONS, not members (a member with two stopped interventions counts twice), including ones this campaign did not prompt, although the sentence says 'member(s)'.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfAppears only when an eligible member has an intervention marked unreachable, declined or cancelled.

app/services/campaigns.py · funnel()

Launch message ('Campaign launched' / 'Campaign topped up': N new task(s), M already had open work, K combined, J sent to providers)

Toast after 'Generate work' in the Launch modal

Formula

eligible = members with a live gap on the measure (re-evaluated first) or the cohort's members skipped = those already holding open work from a live campaign on the same target · combined = those folded into another campaign's open task · new tasks = the rest routed to providers: one opportunity per member with an attributed provider; the others become care-manager tasks

Why this wayLaunching creates tasks (or provider opportunities) and nothing else: no message, no intervention, no gap change. A first launch's message states no counts; a top-up states new and skipped; combined, kept-separate and provider counts are added when non-zero, so a smaller task count is not read as a smaller reach.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfLaunch any quality push, then launch it again: the second message reads 0 new task(s) and N already had open work.

app/services/campaigns.py · launch()app/static/app.js · campaignCreate()

Care-team delivery (person count and order)

Care-team delivery panel, title count and rows

Formula

distinct people who own a task or an intervention, raised an internal referral, or own a vendor referral (any status, all time); sorted by name

Why this wayDeliberately no composite score and no ranking: the customer asked for progress toward outcomes, and a leaderboard over a handful of people is a statistic about nothing. Outcome columns come first; activity is labelled as context.

In the demoJane Smith

app/services/productivity.py · by_owner()

Gaps closed (per person)

Care-team delivery table, Outcomes · Gaps closed

Formula

number of the person's owned interventions whose care gap is now CLOSED

Why this wayCredited to the intervention owner, not caused by them: the measure engine closes gaps on clinical evidence that may arrive for other reasons, and the panel says so. It counts interventions, so two interventions on one closed gap count 2, and a cancelled intervention on a gap that closed still counts. All time.

In the demoJane Smith 0

app/services/productivity.py · by_owner()

Services completed ('X of Y · Z%')

Care-team delivery table, Outcomes · Services completed

Formula

X = the person's owned interventions at COMPLETED or GAP_CLOSED · Y = every intervention they own, any status Z = round( 100 × X ÷ Y, 1 ), omitted when Y = 0

Why this wayY includes interventions still in flight, so a new owner's rate starts low; the referral rate beside it uses only settled referrals. All time.

In the demoJane Smith 0 of 0

app/services/productivity.py · by_owner()

Referrals (loop closed): 'Z% · X of Y settled · R raised'

Care-team delivery table, Outcomes · Referrals

Formula

R = internal referrals the person raised + vendor referrals they own, any status Y = those settled (internal OUTCOME_CONFIRMED, DECLINED, CANCELLED, UNABLE_TO_CONTACT; vendor CONFIRMED, DECLINED, FAILED, CANCELLED) X = internal OUTCOME_CONFIRMED + vendor CONFIRMED · Z = round( 100 × X ÷ Y, 1 ); 'none settled' when Y = 0

Why this wayCredited to whoever RAISED the handoff: the question is whether the referral a coordinator started produced an outcome for the member. In-flight referrals are neither success nor failure. The Operational KPIs 'Referral completion' card also counts internal COMPLETED as a success, so the two can differ. See Part I, Referral SLA.

In the demoJane Smith 100% · 1 of 1 settled · 3 raised

app/services/productivity.py · by_owner()

Tasks open ('N of M')

Care-team delivery table, Flow · Tasks open

Formula

N = the person's tasks at open · M = every task they own, any status (open, done, dismissed), all time

Why this wayM is a lifetime total, not current workload. Completed and dismissed counts are computed but not shown.

In the demoJane Smith 31 of 34

app/services/productivity.py · by_owner()

Overdue (per person) and the OVERDUE badge in the 'Work' drill

Care-team delivery table, Flow · Overdue; 'Work' drill modal, task Status

Formula

a task is overdue when it is open, has a due date, and that date is before today (business date)

Why this wayA task due today is not overdue and a task with no due date never is. The drill applies the same rule row by row, so the badges in it add up to the column.

In the demoJane Smith: 8 (drill badges: 8)

Check it yourselfOpen 'Work' for the person and count the red OVERDUE badges.

app/services/productivity.py · by_owner()app/services/productivity.py · owner_work()

Median hours to first touch (and 'Hours to first touch' in the drill)

Care-team delivery table, Flow · Median hours; 'Work' drill, interventions table

Formula

per owned intervention: hours from its intervention.create audit event to the first later intervention.* audit event, rounded to 2 decimals (dropped unless strictly later) table value = median of those, rounded to 1 decimal; '—' when none

Why this wayMeasured from the append-only audit trail, not a mutable column, and a median so one item left over a weekend cannot dominate. Known limits: each duration is rounded before the median, which breaks Part I's 'rounded once, at the end'; a 'touch' includes system moves (a member's reply moving the intervention to ENGAGED); and interventions created by a provider accepting an opportunity write no create event, so they are left out.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfStart an intervention and move it once; the drill shows its hours and the table the median.

app/services/productivity.py · by_owner()app/services/productivity.py · _intervention_timings()app/services/productivity.py · owner_work()

Median hours to close a task

Care-team delivery table, Flow · Median hours (second line)

Formula

per owned task with a task.done audit event: hours from the task's creation to its FIRST task.done, rounded to 2 decimals value = median, rounded to 1 decimal; the line is hidden when there is none

Why this wayFor a task the first action is also the last (open → done), so this is the task's 'time to first action' under its accurate name. Dismissed tasks, open tasks, and tasks closed without an audit row (e.g. seeded as done) are left out. Same double rounding as first touch.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfIn the demo Jane Smith's 3 done tasks were seeded done, with no task.done event, so the line is hidden until a task is closed on screen.

app/services/productivity.py · by_owner()app/services/productivity.py · _task_close_times()

Response ('Z% replied · X of Y contacted')

Care-team delivery table, Activity (context)

Formula

Y = distinct members the person sent an outbound message to · X = those members with any inbound message Z = round( 100 × X ÷ Y, 1 ); 'no contacts made' when Y = 0

Why this wayContext, never the headline. Known limit: 'replied' has no timing or thread check, so an inbound message sent before the contact, or answering a colleague, counts, and two people who contacted the same member both get the reply. The Operational KPIs 'Outreach response' is a different figure (per ladder).

In the demoJane Smith no contacts made

app/services/productivity.py · by_owner()

Appointments ('X of Y appts kept')

Care-team delivery table, Activity (context), second line

Formula

Y = every appointment, any status, of members on whom the person owns an intervention · X = those at ARRIVED or COMPLETED 'no appointments booked' when Y = 0

Why this wayNot 'appointments this person booked': it follows the members they own work for. Cancelled and no-show appointments are in Y, whereas the funnel's 'Appointment scheduled' on the same screen excludes them.

In the demoJane Smith no appointments booked

app/services/productivity.py · by_owner()

'Unassigned: N task(s) and M intervention(s) belong to nobody'

Care-team delivery panel, note under the table

Formula

N = tasks with no owner · M = interventions with no owner, in ANY status; the note shows only when either is above 0

Why this wayWork owned by nobody appears in no one's numbers, so it is surfaced. Known limit: done, dismissed, completed and cancelled work is counted too, although the note says it 'goes unworked'; My Work's unassigned count uses open tasks only, so the two can differ.

In the demo1 task(s), 1 intervention(s)

app/services/productivity.py · by_owner()

Analytics and operational KPIs

32 values

The VBC manager's and executive's Analytics screen (prior-authorization volume, approval, turnaround, SLA and appeals; care gaps; the intervention funnel and risk mix), plus three panels any authorized role opens over other screens: the documentation readiness check, the eligibility (271) response and the Ask mdvin answers about authorizations.

PA Volume

Analytics, KPI card 1

Formula

every prior authorization in the tenant, whatever its status (draft, pending, decided, withdrawn, expired)

Why this wayA volume count, not a workload count: finished and withdrawn requests stay in it. It equals the sum of the PA Pipeline table beside it and the length of the Authorizations list for the same role, because all three read the same rows at request time.

In the demo1 authorizations

Check it yourselfAdd up the Count column of the PA Pipeline table; it is the same number.

app/api/analytics.py · pa_analytics()

Approval Rate

Analytics, KPI card 2

Formula

approval % = round( 100 × A ÷ max(1, A + D), 1 ) A = requests whose CURRENT status is APPROVED D = requests whose current status is DENIED or CLOSED

Why this wayCounted on where each request stands now, so a denial overturned on appeal counts as approved. CLOSED is treated as a denial because the only code that writes it today is an upheld appeal; the state machine also allows an approved request to be closed, and if anything ever did that, the approval would count as a denial. PARTIALLY_APPROVED, APPEALED, pending, withdrawn and expired requests are in neither A nor D. When nothing has been decided the card shows 0%, not '—' (the max(1, …) guard), unlike the three cards beside it.

In the demo100%

Check it yourselfIn the PA Pipeline table: Approved ÷ (Approved + Denied + Closed).

app/api/analytics.py · pa_analytics()

Avg TAT (average turnaround, hours)

Analytics, KPI card 3

Formula

mean over requests with both submitted_at and decided_at of ( decided_at − submitted_at ), in hours, rounded to 2 decimals '—' when there are none

Why this wayMeasured from submission, the same start as the decision clock (see Part I, Prior-authorization decision clock). Known limit: decided_at is written by more than a determination. Pending a request writes it and resuming does not clear it; a withdrawal writes it; an overturned appeal overwrites it with the appeal's decision time. So a pended request counts at the moment it was pended, and an overturned one counts at the appeal decision. Read it as time to the first reviewer action, or to the latest appeal, not time to the final determination.

In the demo72h

Check it yourselfThe demo request PA-2026-40188 was submitted 4 days before the reset and decided 1 day before it: 3 days = 72 hours.

app/api/analytics.py · pa_analytics()app/services/workflow.py · decide_pa()app/services/appeals.py · decide_appeal()

SLA Compliance, and 'SLA measured on N, met on M'

Analytics, KPI card 4, and the note under PA Pipeline

Formula

N = requests with submitted_at, decided_at and a decision_due M = those of N with decided_at ≤ decision_due SLA % = round( 100 × M ÷ N, 1 ); '—' when N is 0

Why this wayOnly requests with a recorded decision are judged, so a request that is late and still undecided is not in N yet; it shows on the Authorizations list's red SLA chip instead. The same decided_at limit as Avg TAT applies: a pend counts as meeting the clock at the moment it was pended, and an overturned appeal is compared with the ORIGINAL decision due, which can turn an on-time decision into a miss.

In the demo100% (met on 1 of 1 measured)

Check it yourselfThe note under PA Pipeline prints M and N; M ÷ N is the card.

app/api/analytics.py · pa_analytics()

Appeal Overturn, and 'N decided · M overturned'

Analytics, KPI card 5, and the note under Denial Reasons

Formula

decided = appeals with status UPHELD, OVERTURNED or PARTIALLY_OVERTURNED overturned = decided appeals that are not UPHELD overturn % = round( 100 × overturned ÷ decided, 1 ); '—' when none is decided

Why this wayA partial overturn counts as overturned, because the member got some of the service after the plan first said no. Level 1 and Level 2 appeals are counted alike. As the screen says, a high rate usually points to documentation missing at submission rather than to reviewer error; the readiness check further down this chapter exists to catch that before submission.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo appeal exists in the fresh demo, so the card shows '—'. File and decide one on the Authorizations screen and the card becomes overturned ÷ decided.

app/api/analytics.py · pa_analytics()

Appeals filed

Analytics, note under Denial Reasons

Formula

every appeal in the tenant, any level, any status

Why this wayFiled minus decided is the number of appeals still waiting for a decision.

In the demo0 appeals

app/api/analytics.py · pa_analytics()

Gaps Closed

Analytics, KPI card 6

Formula

care gaps whose status is exactly CLOSED, all time

Why this wayOnly a gap closed by evidence counts. EXCLUDED and NOT_APPLICABLE also take a gap off the open list but are not closures, so they are left out. Other screens show a same-named number computed differently: the Dashboard's 'Gaps closed this month' counts only closures dated this month, My Panel's 'Gaps closed' counts only gaps behind the signed-in care manager's own interventions, and Actions & Outcomes counts members, not gaps. The intervention state GAP_CLOSED in the funnel below is a different record and can differ from this count.

In the demo0 gaps

Check it yourselfEquals the 'closed' entry in the 'care gaps … total (…)' line at the foot of the screen (absent when 0).

app/api/analytics.py · vbc_analytics()

PA Pipeline: count per status

Analytics, PA Pipeline table

Formula

for each status, the number of requests currently in it; only statuses with at least one request are listed

Why this wayA snapshot of where requests stand now, not how many ever passed through a state. The rows add up to PA Volume.

In the demoAPPROVED 1

Check it yourselfFilter the Authorizations list by status; each count matches.

app/api/analytics.py · pa_analytics()app/static/app.js · rAnalytics()

Expedited

Analytics, note under PA Pipeline

Formula

requests whose urgency is expedited, any status

Why this wayExpedited requests run on the 72-hour clock rather than 7 days (see Part I, Prior-authorization decision clock). This counts all of them, open and decided.

In the demo0 requests

app/api/analytics.py · pa_analytics()

Denial Reasons (count per reason code)

Analytics, Denial Reasons panel

Formula

for each denial reason code, the number of requests that carry it, whatever their current status 'No denials recorded.' when none carries a code

Why this wayThe code is kept when a denial is overturned on appeal, so an overturned request still counts under its original reason: the panel answers 'why did we deny', not 'why are requests denied now'. A withdrawal clears the code. The API also counts denial reviews, but that figure is not shown.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo request is denied in the fresh demo. Deny one as the medical director (a reason code is mandatory) and it appears here under that code.

app/api/analytics.py · pa_analytics()

Funnel: Interventions created

Analytics, Intervention Funnel, row 1

Formula

every intervention in the tenant, in any state, including cancelled, declined and unreachable ones

Why this wayThe top of the funnel and the 100% width every other bar is drawn against.

In the demo1 interventions

app/api/analytics.py · vbc_analytics()

Funnel: Outreach or beyond

Analytics, Intervention Funnel, row 2

Formula

interventions whose CURRENT state is OUTREACH, ENGAGED, SCHEDULED, AWAITING_AUTH, AUTH_APPROVED, IN_PROGRESS, COMPLETED or GAP_CLOSED

Why this wayCurrent state, not 'ever reached': an intervention that went UNREACHABLE, DECLINED, REOPENED or CANCELLED drops out even if outreach happened. The name overstates it slightly: a member who phoned in can go straight from identified to engaged, or to awaiting authorization, without any outreach, and still counts here. The Dashboard's Engagement funnel uses the same state sets under different labels ('Outreach', 'Reached', 'Scheduled', 'Completed'), but a separate implementation.

In the demo1 interventions

app/api/analytics.py · vbc_analytics()

Funnel: Engaged or beyond

Analytics, Intervention Funnel, row 3

Formula

interventions whose current state is ENGAGED, SCHEDULED, AWAITING_AUTH, AUTH_APPROVED, IN_PROGRESS, COMPLETED or GAP_CLOSED

Why this wayThe Outreach set without OUTREACH itself. The same current-state limit applies: a DECLINED member leaves the count. The demo's one intervention (Derrick Boateng's retinal exam, authorization approved) counts here although it was created directly at AUTH_APPROVED.

In the demo1 interventions

app/api/analytics.py · vbc_analytics()

Funnel: Scheduled or beyond

Analytics, Intervention Funnel, row 4

Formula

interventions whose current state is SCHEDULED, IN_PROGRESS, COMPLETED or GAP_CLOSED

Why this wayAWAITING_AUTH and AUTH_APPROVED are left out because they come before booking: an approved authorization unlocks scheduling. The count can go DOWN: a no-show sends a scheduled intervention back to outreach, and a completed one can be reopened.

In the demo0 interventions

app/api/analytics.py · vbc_analytics()

Funnel: Completed / gap closed

Analytics, Intervention Funnel, row 5

Formula

interventions whose current state is COMPLETED or GAP_CLOSED

Why this wayCOMPLETED means the service happened; GAP_CLOSED means the evidence also closed the care gap. This is an intervention count, so it can differ from the Gaps Closed card, which counts care-gap records.

In the demo0 interventions

app/api/analytics.py · vbc_analytics()

Funnel bar length

Analytics, Intervention Funnel, bar beside each count

Formula

width % = Math.round( 100 × stage count ÷ Interventions created ); 0 when nothing was created

Why this wayEach bar is that stage's share of ALL interventions created, not the conversion from the stage before it, and it is rounded to a whole percent. The Dashboard's funnel draws its bars against its first stage (Outreach) instead, so the same counts produce different bar lengths on the two screens.

In the demoInterventions created 100%, Outreach or beyond 100%, Engaged or beyond 100%, Scheduled or beyond 0%, Completed / gap closed 0%

app/static/app.js · rAnalytics()

Risk mix (members per tier)

Analytics, note under Intervention Funnel

Formula

for each risk tier, the number of members whose STORED tier is that tier (HIGH ≥ 60 · RISING ≥ 35 · MEDIUM ≥ 15 · LOW)

Why this wayIt counts the tier stored on each member record, the same one the Patient 360 summary and the Dashboard's 'High / rising risk' card read, not a fresh score. See Part I, Clinical risk score and tier. A tier typed in by hand is counted as typed. Tiers with no members are not listed.

In the demoHIGH 2, MEDIUM 5, RISING 4, LOW 2

Check it yourselfThe four numbers add up to the member count on the Patients list (13 in the demo).

app/api/analytics.py · vbc_analytics()app/services/risk.py · rescore()

Care gaps total

Analytics, note under Intervention Funnel

Formula

every care-gap record in the tenant, open and closed, any status

Why this wayA count of stored gap records. It is not the Quality screen's 'open gaps' (D − N per measure), which the measure engine recomputes from members; the two agree in the fresh demo (29) because every stored gap is still open, and drift apart as gaps close, are excluded, or when a member has two records for one measure.

In the demo29 gaps

app/api/analytics.py · vbc_analytics()

Care gaps by status ('open 28, in progress 1', …)

Analytics, note under Intervention Funnel, in brackets

Formula

for each stored status, the number of care-gap records in it; the screen lower-cases the status and replaces underscores with spaces

Why this wayRaw statuses, not grouped into open and closed. Open side: OPEN, IN_PROGRESS, SCHEDULED, AWAITING_AUTH, SERVICE_COMPLETED, REOPENED. Closed side: CLOSED, EXCLUDED, NOT_APPLICABLE (and three statuses nothing writes today). Only CLOSED feeds the Gaps Closed card.

In the demoopen 28, in progress 1

app/api/analytics.py · vbc_analytics()app/static/app.js · rAnalytics()

Documentation readiness: each criterion (Evidenced / In the chart, not attached / Not evidenced)

Launch PA modal (Patient 360) and the Review / Detail modals on Authorizations

Formula

for each criterion of the policy, first match wins: 1 a risk factor or care-gap evidence item whose source is in the criterion's evidence-type list → Evidenced 2 a document of an allowed type already attached to this request → Evidenced 3 such a document in the chart but not attached → In the chart, not attached · otherwise → Not evidenced

Why this wayDeterministic: it points at records and never asserts a clinical fact. Known limit, visible in the demo: the structured match is on the evidence TYPE only, never on what the criterion says. Any lab-sourced risk factor satisfies any lab criterion, so on PA-2026-40188 criterion C3 'Elevated HbA1c or insulin-dependent' shows as Evidenced by 'Total cholesterol above 240'. Read an Evidenced line as 'the record has evidence of this kind', and check the cited item. Structured evidence also outranks documents, so a criterion can be Evidenced with nothing attached to the request.

In the demoC1 SATISFIED (Type 2 diabetes (E11.9)), C2 SATISFIED (meets denominator criteria), C3 SATISFIED (Total cholesterol above 240 (was 242.0mg/dL)), C4 MISSING (nothing in the record)

Check it yourselfEach Evidenced line names the record it used and its source system; open the member's Clinical tab and find it.

app/services/readiness.py · assess()app/services/readiness.py · _match_structured()app/services/readiness.py · _match_document()app/static/app.js · readinessHtml()

Documentation readiness: green or amber box, and the sentence ('N requirement(s) …', 'All N criteria …')

Readiness panel, box colour and first line

Formula

ready (green) = no criterion 'not attached' and none 'not evidenced'; otherwise amber sentence, first that applies: N not attached (naming the first file and its upload date) → N not evidenced (listing their ids) → 'All N criteria in KEY vN are evidenced'

Why this wayAn unattached document is named before a missing one because it is the cheaper fix: one click, versus obtaining new documentation. It inherits the type-only limit of the per-criterion check above, so 'All N criteria are evidenced' can be true of the record's types and still leave a reviewer something to ask for.

In the demonot ready: 1 requirement (C4) cannot be evidenced from the record. Obtain and attach the documentation, or expect the payer to request more information.

app/services/readiness.py · assess()app/static/app.js · readinessHtml()

'Attach these N documents to the request when I submit'

Launch PA modal, checkbox under the readiness rows (before a request exists)

Formula

N = criteria in 'In the chart, not attached'; the checkbox appears only when N > 0 and no request exists yet

Why this wayTicked by default: the documents are already in the chart, so attaching them at submission avoids a pend for missing information. Only documents the check found are attached; nothing is generated.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo seeded chart holds an unattached document of a required type, so the checkbox does not appear in the fresh demo. Upload a clinical note for a member, then launch a retinal-exam request for them: the note appears as 'In the chart, not attached' and N counts it.

app/services/readiness.py · assess()app/static/app.js · readinessHtml()app/static/app.js · loadReadiness()

Prior authorization required? (required / 'No authorization required' / 'We cannot say')

Readiness panel (Launch PA modal and Authorizations)

Formula

no active coverage → cannot say policy: the one pinned on the request if any, else the newest active policy for the coverage's payer and line of business that lists the code; none → cannot say policy says pa_required false → not required · otherwise required

Why this waySilence is never read as 'no': a 'not required' answer must come from a policy that says so (the demo loads one for routine services). 'Cannot say' costs a phone call; a wrong 'no' costs a denied claim. The authorization gate used by next best action and booking answers the same question through a separate function that ignores the pinned policy, so for a request pinned to a since-retired version the two can differ.

In the demoDerrick Boateng 92250: required · John Doe 99213: not required · Ahmed Ali 92250: cannot say

Check it yourselfAhmed Ali's plan (BCBS Texas, commercial) has no policy on file in the demo, so the panel says it cannot tell and asks you to confirm with the payer.

app/services/readiness.py · assess()app/api/pa.py · policy_answer()

Eligibility: which coverage is reported (status, payer, plan, dates)

Eligibility modal (Patient 360, 'Check eligibility'), Coverage and Effective lines

Formula

the member's coverage with the latest end date (effective_to), whatever its status; the check is saved with a new ELG-… transaction number

Why this wayReports what the benefit record on file says; it quotes no price. Known inconsistency: other parts of the product pick 'the' coverage differently. The readiness check and the care-gap chain take the coverage marked active, the Dashboard also requires today to fall inside its dates, and this modal takes the latest end date even when that coverage is terminated. With one coverage per member, as in the demo, all agree.

In the demoACTIVE · UnitedHealthcare MA Choice · 2026-01-01 to 2026-12-31

Check it yourselfThe modal's transaction number is saved; the Coverage tab's 'Last verified with the payer' shows it afterwards.

app/services/eligibility.py · verify()app/api/eligibility.py · check()

Eligibility: Deductible bar ('$met of $deductible')

Eligibility modal, first progress bar

Formula

width % = min( 100, Math.round( 100 × deductible met ÷ deductible ) ); 0 when no deductible is on file

Why this wayBoth dollar figures are read from the coverage's benefit record as filed; only the bar is computed, in the browser, and capped at 100%. A missing figure prints as '$null' rather than 'not on file'.

In the demo$875 of $1500 → 58%

Check it yourselfJohn Doe: 875 ÷ 1,500 = 58.3%, drawn as 58%.

app/static/app.js · eligibilityModal()app/services/eligibility.py · verify()

Eligibility: Out-of-pocket bar ('$met of $max')

Eligibility modal, second progress bar

Formula

width % = min( 100, Math.round( 100 × out-of-pocket met ÷ out-of-pocket maximum ) ); 0 when no maximum is on file

Why this waySame arithmetic as the deductible bar. Known limit: coverage added through the product's own 'Add coverage' form has no out-of-pocket field. The form stores a maximum of three times the deductible, 'met' equal to the deductible met, and copays of $30 (PCP) and $60 (specialist), so for those members this bar and the copay line show values the form derived, not benefits filed by the payer. Seeded coverage carries real benefit figures.

In the demo$2200 of $5000 → 44%

Check it yourselfJohn Doe: 2,200 ÷ 5,000 = 44%.

app/static/app.js · eligibilityModal()app/static/app.js · coverageSave()

Eligibility: 'Prior authorization required for' (codes and policy)

Eligibility modal, amber box

Formula

every policy in the tenant with the reported coverage's exact payer name and line of business, status active and pa_required true; one line per policy with its service codes

Why this wayThe list of services this plan says need authorization, so nobody drafts a request that eligibility would have refused. The payer match is an exact text match, and the 'Add coverage' form takes the payer as free text: coverage typed as 'United Healthcare' matches no policy filed as 'UnitedHealthcare', and the box silently disappears.

In the demoUHC-MA-EYE-014 v3: 92250

Check it yourselfSign in as the policy administrator: the Policy Packages list shows the same active, authorization-required package for UnitedHealthcare Medicare Advantage.

app/services/eligibility.py · verify()app/api/eligibility.py · check()

Eligibility: 'Active authorizations on file' (ref, CPT, auth number, 'to <date>')

Eligibility modal, green box

Formula

the member's authorizations whose status is APPROVED; 'to' = the authorization's valid_to a new approval, or an overturned appeal, is valid from the decision's business date to that date + 180 days

Why this wayValidity is not re-checked here. An authorization past its end date is moved to EXPIRED by the automation sweep (every 15 minutes by default), and until then it still lists. PARTIALLY_APPROVED requests receive no authorization number and never list. Known inconsistency in the demo: the seeded authorization PA-2026-40188 was written with a 90-day window, not the 180 days every real approval receives.

In the demo89 days after the reset

Check it yourselfCheck eligibility on Derrick Boateng, the member with the demo's approved authorization: the box lists PA-2026-40188 · CPT 92250 · AUTH-88213417, valid from the day before the reset to the date shown here.

app/services/eligibility.py · verify()app/services/workflow.py · grant_authorization()app/services/workflow.py · expire_authorizations()

Ask mdvin: 'Which requests are close to their SLA?' (N past the deadline, N due inside 48 hours, 'due in Xd Yh')

Ask panel, opener on the Authorizations screen

Formula

live = requests with a decision_due and no decided_at, soonest first over = live with due < now · soon = the rest due within 48 hours facts: over + soon (or all live when both are empty), at most 6; 'in 2d 3h' / '5h' / '40m' / '1d 4h overdue', whole units, never rounded up

Why this way48 hours is the assistant's own cut; the SLA chip on the Authorizations list goes red under 24 hours and amber under 72, so the two can describe the same request differently. Because pending a request writes decided_at and resuming does not clear it, a pended or resumed request drops off this answer while its clock keeps running.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfIn the fresh demo the only request is already decided, so the answer is 'No authorization has a clock running right now.' Submit a request and ask again: it is listed with its time to deadline.

app/services/assistant.py · _h_at_risk()app/services/assistant.py · _rel()

Ask mdvin: 'What is blocking my authorizations?'

Ask panel, opener on the Authorizations screen

Formula

the request named in the question; else the named or on-screen member's request with the highest reference number runs the readiness check on it; answer, first that applies: a document to attach → N requirements not evidenced → 'Nothing is blocking'; one fact per criterion

Why this wayThe same readiness engine as the panel above, so the two cannot disagree about a request. Known limit: with no request or member named, 'highest reference' is not 'newest', because reference numbers are random; and it answers even for a request that is already decided, as the demo shows.

In the demoPA-2026-40188: 1 requirement(s) that nothing in the record satisfies

app/services/assistant.py · _h_pa_blocked()app/services/assistant.py · _newest_pa()app/services/readiness.py · assess()

Ask mdvin: authorization status facts (Status, 'Urgency (72 hours / 7 days)', Decision due)

Ask panel, any screen, e.g. 'What is the status of PA-2026-40188?'

Formula

Decision due = time to or past decision_due, in whole units as above 'past the deadline' when due < now and no decision is recorded Urgency label = '72 hours' when expedited, else '7 days'

Why this wayThe due time is the decision clock of Part I (see Prior-authorization decision clock). Two limits: the urgency label is fixed text, so a deployment that configures a different clock would see the old numbers here; and the 'Decision due' fact is shown even after the decision is made.

In the demoPA-2026-40188 · APPROVED · standard (7 days)

Check it yourselfThe decision-due fact moves with the clock; compare it with the SLA due date on the Authorizations list.

app/services/assistant.py · _h_pa_status()app/services/assistant.py · _rel()

Ask mdvin: 'N authorizations in total, N still open and N closed out'

Ask panel, any screen, e.g. 'What is the total PA volume?'

Formula

total = every request in the tenant; open = SUBMITTED, IN_REVIEW, PENDED or DRAFT; closed out = total − open facts: one per status, largest first

Why this wayTotal equals PA Volume on the Analytics screen. 'Open' here is not the same set as elsewhere: an APPEALED request is still undecided but counts as closed out, the Dashboard's 'PA blocking care' leaves out DRAFT, and the at-risk answer above leaves out pended requests. Phrasing matters: 'How many authorizations are there?' is answered with one request's status instead.

In the demo1 in total, 0 still open and 1 closed out

app/services/assistant.py · _h_counts()

Provider workspace

8 values

The attributed clinician's home screen (sidebar: My Opportunities), seen by a provider login such as the demo's Dr. P. Osei: the gap-closure opportunities the payer has routed to this provider, grouped by what they need, plus the size of the provider's attributed panel.

Need your answer (KPI card and section count)

KPI card 1, and the count beside the 'Need your answer' section title

Formula

count of the listed opportunities whose status is NEW, VIEWED or MORE_INFO_NEEDED counted in the browser from GET /population/opportunities

Why this wayAn opportunity needs the provider while nobody has answered it (NEW, VIEWED). This screen also counts MORE_INFO_NEEDED, which is the provider's own question waiting on the care team: the same row's Do column says 'waiting on the care team', so the card counts a row that is not the provider's turn. It comes back as VIEWED when the care manager answers. The VBC manager's Provider responses panel counts the same three statuses as 'Awaiting'. The list is only this provider's opportunities (see 'Which opportunities are listed' below).

In the demo2 opportunities

Check it yourselfThe section below shows the same count and that many rows. As the VBC manager, Actions & Outcomes > Provider responses > Awaiting for Dr. P. Osei shows the same number.

app/static/app.js · rProvider()app/api/population.py · list_opportunities()

In progress (KPI card and section count)

KPI card 2, and the count beside the 'In progress' section title

Formula

count of the listed opportunities whose status is IN_PROGRESS, COMPLETED or EVIDENCE_RETURNED

Why this wayIN_PROGRESS means the provider accepted and the intervention was created in their name; COMPLETED means they marked the service done and the evidence is still to come. EVIDENCE_RETURNED is listed but never stays: both paths that reach it (already completed with evidence, return evidence) move the opportunity on to CLOSED in the same request, and evidence that does not match the measure is refused before it gets there. In practice this card is IN_PROGRESS + COMPLETED.

In the demo0 opportunities

Check it yourselfAccept an opportunity in 'Need your answer': it moves to this section, and both counts change by one.

app/static/app.js · rProvider()app/services/opportunities.py · return_evidence()

Overdue (KPI card) and the red 'overdue <date>' badge

KPI card 3; the badge replaces the date in a row's Due column

Formula

a row is overdue when its due date is strictly before today's business date AND its status is not settled (CLOSED, WITHDRAWN, DISPUTED, NOT_CLINICALLY_APPROPRIATE) KPI = number of listed rows flagged overdue

Why this wayComputed by the server on every read, never stored; the due day itself is not overdue. The clock is set once at creation and never stopped, so an opportunity the provider has already accepted (IN_PROGRESS), marked done (COMPLETED), or is waiting on the care team for (MORE_INFO_NEEDED) still turns overdue on its date. The caption says 'past the response deadline', but the count means 'past the deadline and not finished', not 'unanswered'. A second rule reads the same deadline differently: the 'unanswered' task raised to the VBC manager fires ON the due day and only for NEW or VIEWED. So for one day a task can say the opportunity is past its deadline while this screen shows no badge, and an accepted opportunity can be overdue here with no task raised. The Provider responses panel's Overdue column uses this screen's rule. See Part I, Provider response metrics.

In the demo0 opportunities

Check it yourselfThe KPI equals the number of red badges in the three sections. The VBC manager's Provider responses > Overdue for Dr. P. Osei shows the same. A freshly reset tenant has none, because the seeded opportunities are due 7 days after the reset.

app/services/opportunities.py · row()app/static/app.js · _oppTable()app/services/task_rules.py · _r_opportunity_unanswered()

My panel

KPI card 4

Formula

number of members GET /vbc/patients returns for this login: members with a PatientAttribution to this provider (provider_id, not the name) in any period whose status is open, with no ended_on

Why this wayThe same list and count as the provider's My Panel screen (see the Patients chapter, 'My Panel (N), provider sign-in'). The caption says 'this period', but the rule is any open period, not the date-bounded current period that the Patient 360 attribution line uses. With one open period (the demo's MY2026) the two agree. With two open periods, a member attributed in either one is counted. If the request fails, the browser falls back to an empty list and shows 0 instead of an error, so a 0 here does not by itself prove an empty panel.

In the demo5 members

Check it yourselfClick My Panel in the sidebar: its count badge shows the same number.

app/core/scope.py · _provider_panel()app/api/vbc.py · list_patients()app/static/app.js · rProvider()

Settled (section count)

Count beside the 'Settled' section title

Formula

count of the listed opportunities whose status is settled: CLOSED, WITHDRAWN, DISPUTED or NOT_CLINICALLY_APPROPRIATE

Why this waySettled means the provider's part is on the record, not that the gap is resolved. DISPUTED and NOT_CLINICALLY_APPROPRIATE land here, but each one raises a High-priority task for the VBC manager that is still open, and the gap itself is not changed: settled for the provider, unresolved for the payer. MORE_INFO_NEEDED is not settled. WITHDRAWN is the payer taking an opportunity back. The three sections split the list with no overlap and no gaps: every status belongs to exactly one of them.

In the demo0 opportunities

Check it yourselfDispute an opportunity with a reason: it moves from 'Need your answer' to 'Settled', and a High-priority 'Reconcile the disputed gap' task appears in the VBC manager's queue.

app/static/app.js · rProvider()app/services/opportunities.py · row()

Due (response deadline)

Due column of every section

Formula

business date on which the opportunity was created + response_days calendar days response_days = tenant setting provider.response_days, default 7; a negative value counts as 0, and a value that is not a number falls back to 7 set once at creation, never moved

Why this wayThe deadline counts calendar days, not business days, so it can fall on a weekend. It is not restarted when the care team answers a question, so a provider can get back an opportunity that is already past due. Sending a gap again while this provider already has a live opportunity on it returns the existing opportunity, with its original deadline.

In the demo7 days after the reset

Check it yourselfSend a gap to the provider as the care manager: the new row's Due date is the reset day plus 7.

app/services/opportunities.py · create()app/services/opportunities.py · response_days()

Which opportunities are listed, and their order

Rows of all three sections

Formula

the tenant's opportunities whose provider_id is the signed-in provider's, then only those whose member is on the provider's panel (the My panel rule) sorted: unsettled first, then by due date, earliest first (no due date last)

Why this wayThe provider comes from the login's binding, never from a query parameter. A provider login bound to no provider sees nothing, not the whole tenant. The second filter has a cost: if a member leaves the panel, every opportunity for that member disappears from this screen, settled ones included. The VBC manager's Provider responses panel is not filtered by panel, so its 'Sent' count for this provider can then be higher than the rows here. Staff logins get the whole tenant from the same endpoint.

In the demoJohn Doe, CDC-HBA1C, Sarah Martinez, EED-RETINAL

Check it yourselfAs the VBC manager, Provider responses > Sent for Dr. P. Osei equals the total number of rows in the three sections here.

app/api/population.py · list_opportunities()app/services/opportunities.py · list_rows()app/core/scope.py · filter_patient_rows()

Do column: which actions a row offers

Do column of every section

Formula

NEW or VIEWED → Accept · Already completed · Need info · Not appropriate · Dispute IN_PROGRESS → Mark service done · COMPLETED → Return evidence MORE_INFO_NEEDED → 'waiting on the care team' · settled → 'settled' · anything else → '—'

Why this wayThe screen only draws the actions the server accepts at that status, and the server still checks each one: 'Already completed' needs evidence that matches the measure, and 'Not appropriate' and 'Dispute' need a reason. Two differences from the server: it also accepts evidence while an opportunity is IN_PROGRESS, but the screen offers Return evidence only at COMPLETED. And there is no 'opened' action, so an opportunity never becomes VIEWED just because the provider looked at it. It shows as Viewed only after the care team answers a Need info question.

In the demoJohn Doe: Accept, Already completed, Need info, Not appropriate, Dispute, Sarah Martinez: Accept, Already completed, Need info, Not appropriate, Dispute

Check it yourselfClick Already completed and leave the evidence empty: nothing is sent. Enter evidence that does not match the measure: the server refuses it and the gap stays open.

app/static/app.js · _oppActions()app/services/opportunities.py · respond()

Exception Center

9 values

The VBC manager's (and executive's) view of what went wrong: open rule-raised work that is an exception rather than routine, grouped by kind with its age, escalation and lateness, plus the two integration failures that are not per-member work. Care managers, providers and auditors cannot open it (the API answers 403).

Open exceptions (and the Open column per category)

KPI card 1, top row; Open column of the By category table

Formula

per category: open tasks (status open) raised by the rule engine (source = 'rule') whose rule_key is one of the 15 keys in EXCEPTION_RULES card = the sum of the Open column over every category

Why this wayThe Exception Center is a view over tasks the rule engine already raised, not a separate store, so an exception is resolved exactly when its task is done or dismissed. Which rules count is an explicit allow-list (pa.sla_at_risk, pa.denied_appealable, vendor.sla_breach, referral.sla_breach, referral.awaiting_acceptance, engagement.exhausted, engagement.stalled, hra.overdue, coverage.lapsing, sdoh.barrier_unaddressed, opportunity.unanswered, gap.evidence_missing, appointment.not_rebooked, opportunity.disputed, opportunity.review), so a new routine rule cannot flood this screen. The cost is the other direction: a rule left off the list never appears here, whatever it says. For example, 'Abnormal result with no follow-up' and 'Enrolled member with no care manager or no care plan' tasks sit in the team queue in the demo tenant but are not counted here, because those two rules are not on the list. The integration failures below are not included in this card.

In the demo6 = 2 HRA past its contractual window + 3 Social barrier with nothing done about it + 1 Vendor has missed its SLA

Check it yourselfthe Open column adds up to the card. Click 'open queue' on a row: the team queue opens filtered to that rule and lists the same number of tasks.

app/services/exceptions.py · summary()app/static/app.js · rExceptions()

Aged ('open 7+ days') and the Aged column

KPI card 2; Aged column of the By category table

Formula

an open exception task is aged when business today − the business date it was created ≥ 7 calendar days (AGED_DAYS = 7) a task with no creation time never ages · card = the sum of the column

Why this wayAged measures how long the TASK has been open, not how long the underlying problem has existed. After the nightly demo reset every task is re-created that day, so Aged is 0 even when a task says an assessment was due 60 days ago. It also runs on a different clock from escalation, which counts from the task's due date when it has one (see Escalated). The 7 days is a constant in the code, not a tenant setting, and the card caption repeats it as fixed text.

In the demo0 tasks

Check it yourselfin the fresh demo tenant every Oldest date is the reset day, so Aged is 0 everywhere. A task created on day D becomes aged on day D + 7.

app/services/exceptions.py · summary()

Escalated ('raised to a second role') and the Escalated column

KPI card 3; Escalated column (amber badge when above 0)

Formula

an open exception task is escalated when a task.escalated audit row exists for it card = the sum of the column

Why this wayEscalation is read from the audit trail, never stored on the task, so it cannot be set by hand. The rule that writes it: an open rule task whose rule sets escalate_after_days and escalate_to_role, once today − (its due date, else the business date it was created) ≥ escalate_after_days, is raised one priority band and gets '[escalated to <role>]' added to its reason, once. As shipped only gap.evidence_missing and appointment.not_rebooked set these (7 days, vbc_manager), so the other 13 categories show 0 unless a tenant adds the parameters. The caption 'raised to a second role' overstates it: the owner does not change and nothing is reassigned; the role appears only as text in the reason. See the My Work chapter, 'Escalated priority'.

In the demo0 tasks

Check it yourselfnone in a fresh tenant. After a rule sweep, an 'evidence missing' or 'missed visit not rebooked' task 7 days old is counted here, and in the queue its priority is one band higher and its reason ends '[escalated to vbc_manager]'.

app/services/exceptions.py · summary()app/services/task_rules.py · _escalate_overdue()

Overdue ('past their due date') and the Overdue column

KPI card 4; Overdue column (red badge when above 0)

Formula

an open exception task is overdue when its due date (first 10 characters, read as an ISO date) is strictly before business today no due date, or one that cannot be read → never overdue · card = the sum of the column

Why this wayLateness is computed from the task on every read, never stored. Due today is not overdue. This counts exception tasks only; the Command Center's 'Tasks overdue' card uses the same test over EVERY open task, so it is normally the larger number. The due date is the one the rule copied from the triggering record (for 'HRA past its contractual window', the assessment's own due date).

In the demo2 (2 HRA past its contractual window)

Check it yourselfclick 'open queue' on a category with a red badge: that many of its rows carry the red OVERDUE badge (the queue applies the same test to the same ten characters of the due date).

app/services/exceptions.py · summary()

By category N (number of categories)

Title of the By category table

Formula

number of distinct rule_key values with at least one open exception task each category's title and grey 'why' line come from the rule catalogue (the 'why' cut to 120 characters)

Why this wayCategories are rules, not a separate taxonomy, so each one maps to exactly one filter on the team queue. A rule with no open task is not listed at all (there are no zero rows); when no category is open the table is replaced by 'No open exceptions', while the four cards still show 0.

In the demo3 categories

app/services/exceptions.py · summary()

Row order of the By category table

By category table, top to bottom

Formula

sorted by Escalated (most first), then Overdue (most first), then Open (most first); a full tie keeps the order the tasks were read

Why this wayEscalation is the strongest signal because a rule already decided the task had waited too long; lateness comes next; volume last, so a large pile of on-time work does not bury one late item. The order is the only output; no rank is shown.

In the demoHRA past its contractual window (esc 0, overdue 2, open 2), Social barrier with nothing done about it (esc 0, overdue 0, open 3), Vendor has missed its SLA (esc 0, overdue 0, open 1)

Check it yourselfthe first row has the most escalated tasks; among equal escalated counts, the most overdue.

app/services/exceptions.py · summary()

Oldest (per category)

Oldest column of the By category table

Formula

the earliest business date on which any of the category's open exception tasks was created, as an ISO date '—' when none of its tasks has a creation time

Why this wayIt is the creation date of the oldest open TASK, in the tenant's business time zone, not the due date of the underlying record and not when the problem began. It is the same clock as Aged, so a category whose Oldest date is 7 or more days ago has at least one aged task.

In the demothe reset day

Check it yourselfin the fresh demo tenant every category shows the reset day, because the reset re-creates every task.

app/services/exceptions.py · summary()

Dead-letter events ('webhook deliveries that gave up')

Integration failures panel, card 1

Formula

number of the tenant's outbox events that were never published and have used all MAX_ATTEMPTS = 8 delivery attempts all-time: no date window

Why this wayAfter 8 failed attempts the relay stops retrying an event and leaves it unpublished, so it stays in this count for as long as it stays unpublished; there is no age limit and no dismissed state. The administrator's Webhooks screen shows the same events ('Dead-letter: N') with the same test, but its limit of 8 is typed separately in its own code rather than read from the relay's constant.

In the demo0 events

Check it yourselfzero in the fresh tenant (the demo relay is off). The administrator's Webhooks screen shows the same number.

app/services/exceptions.py · summary()app/services/relay.py · drain()

Failed ingestion batches ('files with rows that did not load')

Integration failures panel, card 2

Formula

number of the tenant's ingestion batches with rows_failed > 0 all-time: no date window, batch status not considered

Why this wayA batch that had even one failed row counts here permanently: there is no resolved flag, so re-loading the failed rows successfully in a later batch does not remove the earlier one. It reads as 'batches that have ever had a failure', not 'failures waiting now'.

In the demo0 batches

Check it yourselfzero in the fresh tenant. On the Integrations screen, batches with a non-zero Failed column are these; that list shows only the latest 25 batches, so over a long history this card can be larger than what the list shows.

app/services/exceptions.py · summary()

Automation

11 values

The VBC manager's (and the care manager's) check on the task rules: which rules are on, how much work each has raised, what happened to that work, and what the rules would raise if they ran now. Figures are shown as the VBC manager Alex sees them; the care manager sees the same numbers.

Rules on (X/Y), and the Rules table count

KPI card 1 ('watching for work'); the count next to the Rules panel title

Formula

X = catalogue rules whose tenant setting has enabled = true Y = number of rules in the product catalogue (CATALOGUE), on or off the Rules table has one row per catalogue rule, so its count is also Y

Why this wayThe catalogue is fixed in the product: a tenant can turn a rule off or change its priority, owner role and thresholds, but cannot add a rule with no implementation behind it. Every rule starts on. Known limit: three rules (opportunity.review, opportunity.disputed, opportunity.more_info) are raised by a provider's response, not by the sweep. They count as 'on' and 'watching for work' although the sweep never proposes anything for them.

In the demo29/29 (table: 29 rules)

Check it yourselfCount the rows in the Rules table: there are Y. As the VBC manager, each row's button says 'Turn off' when the rule is on; the care manager sees an ON badge instead.

app/services/task_rules.py · outcomes()app/services/task_rules.py · ensure_rules()

Raised

KPI card 2 ('tasks created by rule'); Rules table, Raised column

Formula

per rule: number of tasks carrying that rule_key, in any status (open, done or dismissed), all time card = sum of the Raised column over the catalogue rules

Why this wayAll time, with no date window, so the number only grows. Each rule raises at most one task per triggering record (the store enforces one task per tenant, rule and trigger), so a rule's Raised is the number of distinct records that ever triggered it. Known limit: two features write rule-keyed tasks under keys that are not in the catalogue (hra.<finding> from an HRA finding, engagement.evidence_retrieval from an Engagement outcome). They appear in no row and are not in this card, but they are in the Unassigned card. There are none in the fresh demo tenant. Coincidence in the demo: 29 tasks raised and 29 rules are unrelated numbers.

In the demo29 (transition.contact 1, transition.visit 1, transition.med_rec 1, transition.assessment 1, transition.barriers 1, gap.due_soon 1, lab.critical_result 6, sdoh.barrier_unaddressed 3, hra.overdue 2, vendor.sla_breach 1, enrollment.offer 1, enrollment.started_without_owner 8, engagement.outcome_untriggered 2) tasks

Check it yourselfAdd up the Raised column: it equals the card.

app/services/task_rules.py · outcomes()

Open

KPI card 3; Rules table, Open column

Formula

per rule: tasks with that rule_key whose status is exactly open (a started task is still open) card = sum of the Open column

Why this wayOpen means not yet settled, whether or not someone has started it. It is narrower than My Work's 'raised by a rule' count, which counts every open task not typed by hand (campaign and transitions tasks included) and is based on the task's source, not its rule. In the demo the two agree (26) because every non-manual open task was raised by a catalogue rule. Known limit: the status is compared case-sensitively here, while the sweep lower-cases it; a task stored as 'Open' would count in Raised and in no other column. None exist in the demo tenant.

In the demo26 tasks

Check it yourselfAdd up the Open column: it equals the card. My Work, Team view, shows the same tasks with an AUTO badge.

app/services/task_rules.py · outcomes()

'N overdue' (card subtitle) and the red 'N overdue' badge

Under the Open card; Rules table, next to a rule's Open count

Formula

an open rule task is overdue when its due date (first 10 characters, read as a date) is before today's business date (America/New_York) per rule badge: shown only when N > 0 · card subtitle = sum over rules

Why this wayThe same boundary as My Work's OVERDUE badge: a task due today is not overdue. Two rules (gap.evidence_missing, appointment.not_rebooked) raise tasks with no due date, so their tasks can never be overdue here; those two rules escalate by age instead (see My Work, 'Escalated priority').

In the demo7 (gap.due_soon 1, lab.critical_result 4, hra.overdue 2) tasks

Check it yourselfThe red badges in the Open column add up to the card subtitle.

app/services/task_rules.py · outcomes()app/static/app.js · rAutomation()

Completed (card)

KPI card 4 ('settled as done')

Formula

number of catalogue rule tasks with status done, all time

Why this wayA task is done when a person completes it, or when the platform completes it because the care gap it was raised for closed on evidence. Note on the same screen: the Rules table's column also headed 'Completed' is not this count but a percentage (next row), so the card and the column are different quantities under one label.

In the demo3 tasks

Check it yourselfIn the demo the three done tasks are Sam Okafor's post-discharge contact, medication reconciliation and barriers steps: those three rules show 100% in the Completed column, and the Transitions screen shows the same steps done.

app/services/task_rules.py · outcomes()

Completed % and its badge colour

Rules table, Completed column

Formula

completed % = round( 100 × done ÷ (done + dismissed) ); '—' when done + dismissed = 0 badge: red when dismissed % > 50 · else green when completed % ≥ 60 · else grey

Why this wayThe denominator is settled work only. Counting open tasks as failures would make a rule look worse the more recently it fired. The cost: a rule with one settled task shows 100% or 0% on that one task, so the percentage says nothing about volume; read it with Raised. Rounding is Python's, half to even, so 1 of 8 (12.5) prints 12 where a spreadsheet prints 13.

In the demotransition.contact 100%, transition.med_rec 100%, transition.barriers 100%

Check it yourselftransition.contact: 1 done, 0 dismissed, so 100% and a green badge. A rule with nothing settled shows '—' in grey.

app/services/task_rules.py · outcomes()app/static/app.js · rAutomation()

Dismissed %

Rules table, Dismissed column

Formula

dismissed % = round( 100 × dismissed ÷ (done + dismissed) ); '—' when nothing is settled completed % + dismissed % = 100 (±1 from rounding)

Why this wayThe screen reads a high dismissal rate as a rule raising work the team does not believe in. Known limit: the platform also sets tasks to dismissed. For seven rules (gap.due_soon, sdoh.barrier_unaddressed, engagement.exhausted, engagement.escalate, coverage.lapsing, pa.sla_at_risk, pa.denied_appealable), an open task nobody has started is withdrawn as 'dismissed' when its trigger stops being true, for example because the authorization was decided, coverage was renewed or a referral for the social barrier started. So this column mixes 'the team rejected it' with 'it resolved itself', and a rule whose work mostly resolves itself can show red. The withdrawn task's reason ends '— withdrawn <date>: no longer applies', which is how to tell them apart. (A care gap closed on evidence is different: its tasks are marked done, not dismissed.)

In the demotransition.contact 0%, transition.med_rec 0%, transition.barriers 0%

Check it yourselfNo task is dismissed in the fresh tenant, so every settled rule shows 0%. Dismiss a rule-raised task in My Work and reload: that rule's Dismissed column moves and its Completed column falls by the same amount.

app/services/task_rules.py · outcomes()app/services/task_rules.py · retire_stale()

Unassigned

KPI card 5 ('in the team queue')

Formula

number of tasks with ANY rule_key (catalogue or not) whose status is open and that have no owner_id

Why this wayA rule gives a task to the member's current owner first, then to the holder of the rule's role with the fewest open tasks, and leaves it unowned only when nobody holds that role. Two known differences. (1) This card is not limited to the catalogue, unlike Raised and Open, so an unowned HRA-finding or evidence-retrieval task is counted here and in no other card. (2) My Work's 'unassigned' counts every open task with no owner, typed-in ones included. In the demo tenant this card reads 0 while My Work reads 1, because the one unowned task was typed in by hand.

In the demo0 (My Work unassigned: 1) tasks

Check it yourselfMy Work, Team view: count the UNASSIGNED badges on rows with an AUTO badge and a rule; that is this card.

app/services/task_rules.py · outcomes()app/services/task_rules.py · _assign()

Would raise now, and 'N already raised'

KPI card 6; the count next to 'What would be raised right now'

Formula

candidates = one evaluation pass of every ENABLED rule, today would raise = candidates whose (rule, trigger) has no task yet, in ANY status already raised = candidates − would raise '—' and 'preview unavailable' when the preview call fails

Why this wayIt shows the size of a change to somebody else's queue before it happens. Three known limits. (1) A trigger whose task was completed or dismissed counts as 'already raised' and is never raised again, so a recurring problem on the same record does not come back as new work. (2) 'Run now' repeats the pass up to 4 times so rules that feed each other settle, so a run can create more than this card predicted. (3) The preview is not purely a read: the same evaluation moves stalled outreach ladders to their next channel, opens ladders and refreshes enrolment eligibility, and the preview request saves those changes (see the defect list). 'Already raised' can be lower than Raised: in the demo the trigger of one 'enrolled but no active care plan' task (enrollment.started_without_owner) no longer holds, and that rule does not withdraw its tasks, so 28 of the 29 are still live.

In the demo0 would raise, 28 already raised

Check it yourselfPress Run now as a user who may run it: the toast's 'N task(s) created' is at least the card's number, and the card then reads 0.

app/api/population.py · task_rule_preview()app/services/task_rules.py · evaluate()

What would be raised right now: the rows, their priority and due date

Preview panel under the Rules table (hidden when nothing would be raised)

Formula

the first 10 new candidates, in the order the rules are evaluated (not sorted by priority or due date) priority = the candidate's own grade where the rule sets one (e.g. abnormal lab), else the rule's configured priority due = the date the rule computes (see My Work, 'Due date of a rule-raised task'); '—' when none

Why this wayA sample, not the list: the panel's count is the full number, and only 10 rows are shown. Because the order is evaluation order, the most urgent candidate is not guaranteed to be among the 10.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNothing would be raised in the fresh tenant, so the panel is hidden. It appears when a rule finds a trigger with no task yet, for example after a rule is turned back on or new data is loaded, and it lists what Run now would create.

app/api/population.py · task_rule_preview()app/services/task_rules.py · evaluate()app/static/app.js · rAutomation()

'N task(s) created' / 'Nothing new' (Run now)

Toast after pressing Run now

Formula

N = tasks created across up to 4 evaluation passes, stopping at the first pass that creates nothing; a (rule, trigger) that already has a task is skipped after the passes, open unstarted tasks of the seven withdrawable rules whose trigger is gone are dismissed, and overdue tasks of rules with an escalation setting move up one priority band

Why this wayRunning repeatedly is safe: each trigger raises at most one task, so a second press says 'Nothing new'. The run result also reports how many tasks were withdrawn and escalated and whether the passes settled, but the toast shows only N. The same run happens on a timer and on every ingestion, so the button is for watching it happen.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfProduced only by pressing Run now, which writes. Known defect: the button is shown to users who can write member work (the care manager) and hidden from the VBC manager, while the endpoint accepts only the VBC manager's permission, so in the demo the person who sees the button gets 'Forbidden'.

app/api/population.py · task_rule_run()app/services/task_rules.py · run()

Prior authorizations

19 values

The UM nurse's and medical director's Review Queue, and the Authorizations screen that the care manager, PA coordinator, provider (own panel only), VBC manager, auditor and administrator open from the same menu item: every prior-authorization request with its status, decision clock, policy and authorization number, the Review and Detail windows behind each row, and the Appeals panel. This chapter also covers the messages the Launch PA flow on Patient 360 prints, and the notifications bell those decisions feed. The documentation readiness panel, the eligibility window and the Ask panel have their own rows in the Analytics chapter.

Review Queue / Authorizations (count in the heading)

Panel heading. UM nurse and medical director see 'Review Queue'; every other role sees 'Authorizations'

Formula

count = every prior-authorization request in the tenant, in EVERY status, after the provider-panel filter (a provider sees only members attributed to them) rows are listed newest submission first

Why this wayThis is the whole register, not open work. A reviewer's 'Review Queue' count includes approved, denied, withdrawn, closed and expired requests, and only rows in IN_REVIEW get a Review button; all the others get Detail. In the fresh demo the medical director's Review Queue shows 1 and has nothing to review: the one seeded request is already approved. The screen also counts the appeals still in review, and never shows that count.

In the demo1 requests

Check it yourselfCount the table rows: they match the heading, whatever their status. For work actually waiting on a reviewer, count only the rows with a Review button.

app/api/pa.py · list_pas()app/static/app.js · rPAs()

Status (request status badge)

Status column; also the Detail window's title and the determination message

Formula

on submit → IN_REVIEW (it passes through SUBMITTED inside the same save) · approve → APPROVED · deny → DENIED · pend → PENDED · provider responds to a pend → IN_REVIEW · withdraw → WITHDRAWN appeal filed → APPEALED; appeal overturned or partially overturned → APPROVED, upheld → CLOSED APPROVED whose valid-to date is before today (US Eastern business date) → EXPIRED

Why this wayMoves follow one transition table, and the decision paths refuse a move it does not allow. Two paths, the provider's response and the appeal decision, set the status directly, but only to moves the table allows. Only a medical director with an MD credential can deny, and only with a reason code and text. Expiry is applied by the automation sweep (every 15 minutes by default), so an authorization can read APPROVED for up to one sweep after its last valid day. PARTIALLY_APPROVED is a legal state that nothing writes: a partially overturned appeal leaves the request APPROVED, with a full authorization number.

In the demoPA-2026-40188 APPROVED

Check it yourselfApprove a request in the Review window: the toast and the badge both read APPROVED. The Audit Trail has one event for each move.

app/services/workflow.py · submit_pa()app/services/workflow.py · decide_pa()app/services/workflow.py · respond_to_pend()app/services/workflow.py · withdraw_pa()app/services/appeals.py · file_appeal()app/services/appeals.py · decide_appeal()app/services/workflow.py · expire_authorizations()

SLA due clock ('in 6d 22h' / 'overdue 3h 10m')

SLA due column; the chip in the Review window title; 'SLA due <date> (in …)' in the message after Submit PA on Patient 360

Formula

decision due = submitted at + 72 h (expedited) or + 7 days (standard); see Part I, Prior-authorization decision clock remaining = due − now, floored: 'Xd Yh' when 1 day or more, 'Hh Mm' under a day, 'Mm' under an hour; 'overdue …' once past the column shows it only while the status is IN_REVIEW; every other status shows '—'

Why this wayThe clock keeps running while a request is PENDED: answering the pend does not restart it, and the column shows '—' for the whole pend (My Work's at-risk task does watch PENDED requests; see My Work, 'decision due in Hh'). Known defect: the list and the Review window receive the due time without a time zone, and a browser reads a time with no zone as its own local time. The countdown is off by the viewer's offset from UTC: a viewer on US Eastern time sees about 4 hours more than really remains, and a viewer in Pakistan about 5 hours less. The message after Submit PA receives the time with its zone, so it is correct, and for the same request it can disagree with the column by that offset. The 72 h and 7 days come from deployment settings; the labels 'Expedited — 72h SLA' and '7 calendar days' are fixed text.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfThe one seeded request is approved, so the fresh demo shows '—'. Launch a PA for John Doe's retinal exam: for a viewer on UTC the new row's chip reads 'in 6d 23h' (standard), and on US Eastern time about 'in 7d 3h' because of the defect above. Hover the chip to see the due time: it is UTC, printed without a zone.

app/services/workflow.py · submit_pa()app/api/pa.py · list_pas()app/api/pa.py · get_pa()app/static/app.js · relTime()app/static/app.js · slaChip()app/static/app.js · submitPA()

Clock colour (red / amber / green)

Every SLA chip on this screen: the request column, the Review window, the Appeals panel and the Independent appeal review window

Formula

red when overdue or less than 24 h remain · amber when less than 72 h remain · green otherwise

Why this wayFixed thresholds in the browser, the same for request and appeal clocks. An expedited clock is 72 hours long, so it is amber from the moment it starts. These bands only colour the chip. They are separate from the points an approaching deadline adds to a task's rank on My Work (see Part I, Work-queue ranking). The same time-zone defect as the clock applies, so for a viewer far from UTC a chip can turn red hours early or late.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNothing is on a clock in the fresh demo. Submit an expedited request: its chip is amber at once, while a standard request's chip is green.

app/static/app.js · relTime()

Policy and version ('UHC-MA-EYE-014 v3')

Policy column; 'policy … v…' in the Review window; 'Policy snapshot' in the Detail window

Formula

at submission, from the member's active coverage (payer, line of business): among ACTIVE policies for that payer and line of business, newest version first, the first whose service codes list the request's CPT the key and version are frozen on the request; blank when no policy lists the code

Why this wayFrozen so that publishing a new policy version never rewrites the rules an open case is judged under; the decision trace records the same version. When nothing matched, the column prints a bare 'v' with no key: the request was still accepted, with no policy to review it against.

In the demoPA-2026-40188: UHC-MA-EYE-014 v3

Check it yourselfSign in as the policy administrator: Policy Packages lists UHC-MA-EYE-014 v3 as active for UnitedHealthcare Medicare Advantage, with 92250 among its codes.

app/api/pa.py · resolve_policy()app/api/pa.py · create_pa()

PA reference ('PA-2026-40188')

PA column of both tables; window titles; toasts

Formula

'PA-2026-' + a random whole number from 10000 to 99999

Why this wayAn identifier, not a sequence. A gap between numbers is not a missing case, and the numbers carry no order or count. '2026' is fixed text, not the year of submission. Known limit: the space is 90,000 numbers shared by every tenant, and a repeated number fails the submission with a server error instead of drawing again.

In the demoPA-2026-40188

app/api/pa.py · create_pa()

Auth # (authorization number) and its validity window

Auth # column; Detail window; determination message; 'Auth … · valid to <date>' in the approval notification

Formula

on approval, or on an overturned or partially overturned appeal: AUTH- + 8 random upper-case hex characters valid from = the decision's business date (US Eastern); valid to = the business date 180 days later '—' when the request was never approved

Why this wayOne function mints both the number and the window, so a first approval and an overturned appeal cannot disagree on how long an authorization lasts. An overturn starts a new 180 days from the overturn date. The Detail window shows the number but not the dates, although the server returns them; they appear in the approval notification and the eligibility window. Known inconsistency in the demo: the seeded authorization was written directly, not through an approval, so its window is 90 days (valid from the day before the reset to 89 days after it) and not the 180 every real approval receives.

In the demoPA-2026-40188: AUTH-88213417

Check it yourselfApprove a request: the message ends '· AUTH-…', and the care manager's bell shows 'valid to' a date 180 days after today.

app/services/workflow.py · grant_authorization()app/services/workflow.py · decide_pa()app/services/appeals.py · decide_appeal()

AI advisory (model label and note)

Review window, 'AI advisory (…), cannot decide' box, after 'Run criteria analysis'

Formula

criterion outcomes = the readiness rules on the record: evidenced → MET; in the chart but not attached, or not evidenced → UNKNOWN; when the check returns no criteria (no policy found), the browser's fixed C1–C3 MET are stored instead model label = the fixed text apex-um v1.8 note stored = the text the browser sends, and the readiness narrative only when it sends none

Why this wayNo model is involved: the advisory is the deterministic readiness check (see the Analytics chapter's readiness rows), which is why it can cite evidence and never invent it. It records a trace row and cannot change the case. Read the box with care. The label names a model version that does not exist. The browser always sends the same fixed sentence, '3/3 MET on available evidence. Approval probability high', so that sentence is what is stored, even for a four-criterion policy and even when the check found criteria it could not evidence. No approval probability is computed anywhere. On screen the note line is blank, because the window's data does not include the stored note.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo advisory exists in the fresh demo. As the UM nurse, open Review on a request and click Run criteria analysis: the box appears with the label apex-um v1.8, and the Detail window's trace gains an 'ai_advisory' row.

app/api/pa.py · ai_advisory()app/services/readiness.py · to_outcomes()app/static/app.js · runAI()app/static/app.js · reviewPA()

Determination message ('PA-… → APPROVED · AUTH-…') and the criterion outcomes it records

Review window, after Approve, Pend or Deny

Formula

status = approve → APPROVED, deny → DENIED, pend → PENDED; the authorization number is added only on approval criterion outcomes recorded = one of two fixed templates chosen in the window: 'ALL MET' (C1 C2 C3 all MET) or 'C2 UNKNOWN' (C1 C3 MET, C2 UNKNOWN)

Why this wayThe status and number come back from the server, so the message cannot claim what was not saved. The outcomes do not come from the policy. The window offers two fixed three-criterion templates, whatever the policy says. The demo's retinal-exam policy has four criteria (C1 to C4), so a human decision records C4 as neither met nor unknown: it is absent. Read the trace's outcomes as the reviewer's selection, not as an evaluation of each criterion.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfAppears only after a decision. In the demo, sign in as md@demo.payer, Review the care manager's new request and Approve it. The message reads '<ref> → APPROVED · AUTH-…'.

app/services/workflow.py · decide_pa()app/api/pa.py · decision()app/static/app.js · decide()

Decision trace (count)

Detail window, 'Decision trace' heading

Formula

count = every review row on the request, of every kind: human decisions, withdrawals and appeal decisions (all recorded as human), ai_advisory, peer_to_peer and provider_response

Why this wayA count of recorded events, not of decisions: an advisory followed by an approval counts 2, and a peer-to-peer request counts as well. Known defect, visible in the fresh demo: the seeded approved authorization PA-2026-40188 shows a trace of 0 and the word 'none'. It was written into the database directly, not through the approval path that records the reviewer, so an APPROVED case has no recorded decision behind it.

In the demo0 rows

Check it yourselfOpen Detail on PA-2026-40188: 'Decision trace 0', then 'none'. Approve a new request and its Detail window shows 1 human row.

app/api/pa.py · get_pa()app/static/app.js · viewPA()

Decision trace row ('human · MD… | approve · criteria v3')

Detail window, one line per trace row

Formula

left: kind · model (advisory only) · reviewer credential right: disposition (approve deny pend withdrawn info_provided p2p_requested appeal_…; 'advisory' when none) · criteria v = the request's frozen policy version, 0 when it has none

Why this wayThe criteria version is copied from the request, not looked up again, so the trace proves which version of the rules the case was judged under even after the policy moves on. The credential is the reviewer's own, from their login, which is how an adverse decision shows it came from a physician.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfEmpty in the fresh demo (see the count above). After an approval, the row reads 'human · MD-… | approve · criteria v3'.

app/services/workflow.py · decide_pa()app/api/pa.py · ai_advisory()app/api/pa.py · peer_to_peer()app/static/app.js · viewPA()

Appeals (count in the panel heading)

Appeals panel under the request table; the panel is hidden when there are none

Formula

count = every appeal in the tenant, any status and any level, after the provider-panel filter rows are listed newest filing first

Why this wayOpen and decided appeals are counted together, like the request count above. The screen also computes how many are still in review, and never shows that figure. Level-2 external reviews are counted when they exist, but no screen can create one: the endpoint has no button.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo appeal exists in the fresh demo, so the panel is hidden. Deny a request (as the medical director, with a reason), then file an appeal from its Detail window: the panel appears with 1.

app/api/appeals.py · list_appeals()app/static/app.js · rPAs()

Appeal status, and the 'Appeal … — PA …' message

Appeals panel, Status column; message after 'Submit determination' in the Independent appeal review window

Formula

filed → IN_REVIEW · decided: upheld → UPHELD, overturned → OVERTURNED, partially overturned → PARTIALLY_OVERTURNED effect on the request: overturned or partially overturned → APPROVED with a new authorization number · upheld → CLOSED

Why this wayOnly a denied request can be appealed, and only one appeal at a time. The decision is refused unless the reviewer is a medical director with an MD credential who did not issue the original denial, and an uphold needs a reasoned note of at least 10 characters. A partial overturn is treated exactly like a full one: the request becomes APPROVED, not partially approved.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNeeds a denied, appealed request (see the appeals count). Decide it as a different medical director: the message reads 'Appeal overturned — PA APPROVED · AUTH-…'.

app/services/appeals.py · file_appeal()app/services/appeals.py · decide_appeal()app/static/app.js · decideAppeal()

Appeal type ('EXPEDITED 72h' / 'Standard')

Appeals panel, Type column

Formula

'EXPEDITED 72h' when the filer ticked Expedited, otherwise 'Standard'

Why this wayThe '72h' is fixed text in the browser, and so is the '(72h)' in the medical director's notification. The clock actually applied is the tenant's appeal rule (72 hours unless the tenant changes it), so a tenant that sets 24 hours still sees '72h'. A Level-2 external review is never expedited.

In the demo— nothing to show in the fresh demo tenant

app/static/app.js · rPAs()app/services/appeals.py · appeal_rules()

Appeal decision due clock, and 'independent review due in …'

Appeals panel, Decision due column; Independent appeal review window title (expedited appeals only); message after filing an appeal

Formula

decision due = filing time + 72 h (expedited) or + 30 days (standard), from the tenant's appeal rules Level-2 external review: escalation time + 45 days, fixed shown as the same 'in … / overdue …' chip and colours as the request clock, only while the appeal is IN_REVIEW

Why this wayAppeal clocks are a tenant rule, while the request clock is a deployment setting: the two are configured in different places. The review window shows the chip only for expedited appeals. The same time-zone defect as the request clock applies to the panel. The message printed right after filing receives the time with its zone and is correct, so the two can differ for the same appeal by the viewer's offset from UTC.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfFile a standard appeal: the message reads 'independent review due in 29d 23h' or close to it. The panel's chip shows the same due time, shifted by the viewer's offset from UTC.

app/services/appeals.py · file_appeal()app/api/appeals.py · escalate_external()app/api/appeals.py · list_appeals()app/static/app.js · submitAppeal()app/static/app.js · appealReviewModal()

Appeal filing window ('Appeal window closed <date> (N days for this line of business)')

File appeal window, error message; the fixed notes under both panels

Formula

window = the tenant rule's days for the coverage's line of business (Medicare Advantage 60), else its default (180) deadline = the denial's decision time + window days; filing after it is refused, and the message prints the deadline's US Eastern business date

Why this wayLine-of-business rules, not one universal clock, as the panel notes say. The deadline appears here only once it has passed. Two known limits. My Work's denied-authorization task names its own closing date using a flat 60 days for every line of business, so for a non-Medicare-Advantage denial it names a date 120 days earlier than this rule closes the window (see My Work, 'appeal window closes DATE'). And a tenant that sets its own per-line-of-business table replaces the default table entirely, so unless it restates Medicare Advantage's 60 days, Medicare Advantage falls back to the tenant's default window.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfThe demo has no denial, so the window never shows. A Medicare Advantage denial decided on 1 July (US Eastern) can be appealed until the same time of day on 30 August; after that, filing is refused with 'Appeal window closed 2026-08-30 (60 days for this line of business)'.

app/services/appeals.py · appeal_rules()app/services/appeals.py · file_appeal()

'PA REQUIRED under <policy> vN' / 'No PA required' / 'PA requirement UNKNOWN' / 'not checked'

Patient 360, message after starting an intervention on a care gap

Formula

no service code on the gap → 'not checked' otherwise, the member's active coverage and the policy resolver (see Policy and version above): a policy lists the code and requires authorization → REQUIRED · lists it and says not required → 'No PA required' · no policy lists it → UNKNOWN

Why this way'Not required' has to come from a policy that says so. A code that no policy on file lists is 'we do not know', never 'no': an unknown shown as unknown costs a phone call, and an unknown shown as 'no' costs a denied claim. The readiness panel answers the same question with the same resolver (see the Analytics chapter, 'Prior authorization required?').

In the demoPA REQUIRED under UHC-MA-EYE-014 v3

Check it yourselfThe screen asks through POST /pa/requirement-check, which writes an audit row, so this value is read through the readiness pre-check, which uses the same resolver. On John Doe's retinal exam (92250) the demo message reads 'PA REQUIRED under UHC-MA-EYE-014 v3' (runbook step 10).

app/api/pa.py · policy_answer()app/api/pa.py · requirement_check()app/static/app.js · startIntervention()

'N of M found documents attached'

Patient 360, message after Submit PA in the Launch PA window

Formula

M = the documents the readiness check found in the chart but not attached, when 'attach' is ticked (0 otherwise) N = those the browser then attached, plus any already attached to this request the message turns red when N < M

Why this wayAttaching runs after the request is saved, one document at a time, so a failure to attach never loses the request; the message says how many made it. A document already attached counts as attached, because a repeat submission under the same idempotency key finds the request already bound to it, which is the intended end state.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfNo seeded chart holds an unattached document of a required type, so the fresh demo prints no 'N of M'. Upload a clinical note for the member first, then Submit PA: the message ends '1 of 1 found document attached'.

app/static/app.js · submitPA()

Notifications bell (unread count) and its items

Header bell, every screen

Formula

unread = notifications not marked read, among the newest 30 addressed to this user or to this user's role hidden at 0, '9+' above 9 items: a decision → the care manager role; a pend answered → the UM nurse role; an appeal filed or a peer-to-peer request → the medical director role; an appeal decided → the person who filed it; a referral update → the person who created the referral

Why this wayEmitted in the same save as the event, so a notification can never announce something that did not happen. Known limits. Read is one flag on the notification, not per person, so when one care manager opens a role-wide notification it disappears for every care manager. Unread items older than the newest 30 are never counted. A decision notifies the care manager role only, so the PA coordinator or provider who submitted the request is not told.

In the demo— nothing to show in the fresh demo tenant

Check it yourselfThe fresh demo has no notifications. After the medical director approves the care manager's request (runbook step 13), the care manager's bell shows 1: '<ref> approved', 'Auth AUTH-… · valid to <date>'.

app/api/appeals.py · my_notifications()app/api/appeals.py · mark_read()app/services/appeals.py · notify()app/static/app.js · refreshBell()

Member portal

6 values

The member's own view, signed in as the member (the demonstration member John Doe): what care is due, their visits, their prior authorizations, their messages and how they want to be contacted. It deliberately shows no risk tier, cost, contract or internal review detail.

Plan and effective dates (header line)

Header, under the member's name

Formula

the member's first coverage row whose status is active; payer, plan, effective from and to no active coverage → the line is not shown

Why this wayThe member sees the plan the record says is active. Two limits, stated plainly: the query takes the FIRST active row with no ordering, so a member with two active coverages (for example a mid-year plan change not yet closed out) sees whichever the database returns first; and the line carries no verification caveat, while the care team's authorizations panel for the same member says the coverage has never been verified with the payer.

In the demoUnitedHealthcare MA Choice, 2026-01-01 to 2026-12-31

Check it yourselfThe care team's Patient 360 for the same member shows the same payer and plan.

app/api/member.py · profile()

What is due (count and list)

What is due panel, title count and table

Formula

the member's care gaps whose status is OPEN, IN_PROGRESS, SCHEDULED, AWAITING_AUTH or REOPENED (the open-gap states GAP_OPEN_STATES minus SERVICE_COMPLETED); count = number of rows, no limit 'Why' = the reason the gap engine recorded when it opened the gap

Why this wayDeliberately narrower than every staff gap count: a gap in SERVICE_COMPLETED means the member has attended and the evidence is still on its way, so there is nothing left for the member to do and listing it would ask them to go again. The cost: this count can be LOWER than 'Open gaps' on the care team's Patient 360 summary strip for the same person, by the number of gaps awaiting evidence (in the fresh demo tenant both show 5). The list has no ordering (database order, not priority or due date), and the gap's due date is returned by the server but not shown, so an overdue item looks the same as one due at year end.

In the demo5 due: HbA1c Control — Glycemic Assessment; Diabetic Retinal Exam; Statin Adherence (PDC >= 80%); Blood Pressure Control; Annual Wellness Visit

Check it yourselfCount the table rows; open John Doe's Patient 360 as the care manager and compare with the Open gaps card (they differ only by gaps in SERVICE COMPLETED).

app/api/member.py · care()app/static/app.js · rMember()

Your appointments (count and list)

Your appointments panel, title count and table

Formula

every appointment row for the member, whatever its status (BOOKED, CONFIRMED, ARRIVED, COMPLETED, CANCELLED, NO_SHOW) and whether past or future; oldest first; no limit

Why this wayIt is the member's visit history, not a list of upcoming visits: a cancelled or missed visit still counts and still appears, with its status badge. A member reading the count as 'visits coming up' will over-read it once any visit is in the past or cancelled. The fresh demo member has no booked visit, so the panel shows 'No appointments'.

In the demo0 appointments

Check it yourselfWhen the care team books a visit for John Doe (it appears on his Patient 360 appointments list), sign in as the member: the count rises by one; cancelling the visit leaves the row, now marked CANCELLED, and the count unchanged.

app/api/member.py · appointments()

Your authorizations (count, status and reason)

Your authorizations panel, title count and table

Formula

every prior authorization request for the member, any status, in database order; no limit 'Valid' = valid from … to … when an approval set them, otherwise '—' 'Reason' = the recorded denial reason text only

Why this wayThe member sees status, dates and the specific reason they are entitled to for an adverse determination (CMS-0057-F), and nothing from the review itself: no reviewer notes, criteria evaluation or AI advisory, because exposing those would make the portal a discovery surface. The server also returns the submitted and decided dates and the authorization number, which the table does not show. Empty in the fresh demo tenant.

In the demo0 authorizations

Check it yourselfSubmit a prior authorization for John Doe as the provider or coordinator; it appears here with the same reference and status the PA queue shows.

app/api/member.py · authorizations()

Messages (count and list, '(you)')

Messages panel, title count and table

Formula

the member's 25 NEWEST contact-log rows (Engagement), newest first, every channel, both directions, any delivery status count = rows returned, so it never exceeds 25 · '(you)' = the row's direction is inbound

Why this wayOne list across SMS, email, portal and voice, so a reply the member typed sits next to the message it answers. Limits, stated plainly: the count is capped at 25 with no sign that older messages exist; delivery status is not shown, so a message that failed to send still appears as if received; and a finished simulated call appears once, as its transcript on channel voice. The care team's contact history for the same member lists up to 40 entries and adds each call's own record (channel phone), so the two counts differ whenever a call has been made.

In the demo0 messages

Check it yourselfSend an outreach message to John Doe as the care manager, then sign in as the member: it appears at the top. Replying from the portal adds a row marked '(you)'.

app/api/member.py · messages()app/services/engagement.py · history()

How we contact you (SMS / EMAIL / PHONE ticked)

How we contact you panel, checkboxes

Formula

intended: a box is ticked unless the member's consent record says that channel is opted out (channel_opt_in[ch] is False) as built: the profile the screen reads carries no consent record, so every box is ticked

Why this wayDEFECT, stated plainly: the screen reads me.preferences but the profile endpoint never returns it, so all three boxes render ticked whatever the member chose. A member who unticks SMS and saves sees SMS ticked again on reload, and the next Save writes it back as opted in. It also turns 'never asked' into 'opted in': the demo member's consent record has Voice never asked, the box shows it ticked, and one Save records a consent the member never gave. Two smaller gaps: the Portal channel has no box, and the staff screens label this channel 'Voice' where the portal says 'PHONE'. The consent rules themselves are the one consent authority described in Part I, Engagement ladder, wait times and the contact ceiling; the care team's consent table is in the Patient 360: coordination chapter.

In the demoticked: SMS, EMAIL, PHONE · consent record: sms opted in, email opted in, phone never asked

Check it yourselfUntick SMS, Save, reload the page: SMS is ticked again. The care manager's Patient 360 Engagement tab shows the member's real consent per channel.

app/static/app.js · rMember()app/api/member.py · profile()app/services/engagement.py · consent_state()

Admin, integrations, policies and audit

6 values

The platform administrator's and auditor's screens: the tamper-evident audit trail and its verification, inbound data batches, payer policy packages, users and webhooks. Mostly records rather than calculations; the values below are the ones the product computes.

Audit chain verification ('chain intact', events checked, first break)

Audit screen, Verify

Formula

for every audit event in sequence order: hash = SHA-256( previous event's hash + the event's canonical content ); the first event chains from 'GENESIS' intact when every stored hash equals the recomputed one; otherwise the sequence number of the first event that does not match

Why this wayEach event commits to the one before it, so changing, deleting or inserting any past event breaks every hash after it and the check names the first break. Nothing is trusted from storage: every hash is recomputed on each verification.

In the demointact

Check it yourselfPress Verify on the Audit screen: it recomputes the whole chain and reports the count checked.

app/core/audit.py · verify_chain()app/core/audit.py · _digest()

Audit events (total, and the count per action in the filter)

Audit screen, header and action filter

Formula

total = audit events matching the filters (tenant, action, member, date); per-action counts = events grouped by action

Why this wayThe audit trail is append-only; these are plain counts of what it holds, so an auditor can see what kinds of activity exist before filtering to them.

In the demomoves with the clock — read it on screen

app/api/audit.py · list_events()app/api/audit.py · action_vocabulary()

Inbound batches: rows total, promoted, failed, and the batch status

Integrations screen, batches table

Formula

rows total = rows in the file; promoted = rows written into the clinical record; failed = rejected (unparseable value) + unmatched (no member found) status: PROMOTED when any row promoted · FAILED when none promoted and some failed · STAGED otherwise derived from the rows' own statuses every time, so a re-run cannot rewrite the record

Why this wayEvery inbound row is accounted for, one outcome each, with the reason and the raw payload kept for a failed row: in the demo file one row names a member we do not hold and one carries the value 'pending' where a number is required. Known defect being fixed: the batch header's stored promoted/failed counters can lag the rows' own statuses (the header reads 1 promoted, 0 failed for this file); the row list is the authority.

In the demoquest_labs_2026-09-21.csv: 4 rows — 2 promoted, 1 rejected, 1 unmatched

app/services/ingest.py · promote()app/api/integration.py · batches()

Policy packages (count, and which require prior authorization)

Administration → Policy packages

Formula

one row per versioned payer policy package: payer, line of business, state, service codes, whether authorization is required, criteria, status and effective dates

Why this wayPolicy is data, not code: the requirement check, the readiness criteria and the authorization screens all read these rows, so changing a policy changes every screen at once and the version is recorded on each decision.

In the demo3 packages, 2 require authorization

app/static/app.js · rAdmin()

Users (count, by role, MFA enabled)

Administration → Users

Formula

the tenant's user accounts, active or not, with role and whether multi-factor authentication is enabled

Why this wayA plain count of accounts; permissions come from the role (see the manual's permission matrix), never from the user row.

In the demo12 users, 0 with MFA

app/static/app.js · rAdmin()

Webhooks (subscriptions, and the topics you can subscribe to)

Administration → Webhooks

Formula

subscriptions = the tenant's registered endpoints; topics = every event the platform emits to its outbox (authorization, appeal, care gap and intervention events)

Why this wayOnly emitted events are offered, so a subscription can never be to a topic nothing publishes.

In the demo0 subscriptions, 25 topics

app/static/app.js · rAdmin()
mdvin · US healthcare prior authorization and value-based care orchestration · User manual · Architecture · Security · Home
Educational and analytics platform running on synthetic data. Not medical advice, not a coverage determination, and not a substitute for a licensed clinician's judgment.