Skip to content

Commit ec45dd0

Browse files
authored
LEGLINK-796: Update ABS Expectations for Schedule Multi Measure Runs (#1795)
Checkin
1 parent 6333aa8 commit ec45dd0

4 files changed

Lines changed: 66 additions & 29 deletions

File tree

DotNet/Automation.Link/Validation/ReportAbsManifestValidator.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public async Task ValidateAllAsync(
140140
}
141141

142142
HashSet<string>? expectedSubmittedMeasureReportIds = null;
143+
Dictionary<string, int>? terminalReportableMeasureReportCountByPatient = null;
143144
if (!string.IsNullOrWhiteSpace(reportId) && Guid.TryParse(reportId, out var scheduleIdForMeasureReports))
144145
{
145146
try
@@ -159,6 +160,15 @@ public async Task ValidateAllAsync(
159160
.Where(id => !string.IsNullOrWhiteSpace(id))
160161
.ToHashSet(StringComparer.Ordinal);
161162

163+
terminalReportableMeasureReportCountByPatient = entries
164+
.Where(e => !string.IsNullOrWhiteSpace(e.PatientId)
165+
&& expectedSubmittedPatientSet.Contains(e.PatientId)
166+
&& string.Equals(e.SubmissionStatus, "Submitted", StringComparison.OrdinalIgnoreCase))
167+
.ToDictionary(
168+
e => e.PatientId,
169+
e => e.MeasureReports.Count(mr => IsReadyForValidation(mr.Status)),
170+
StringComparer.Ordinal);
171+
162172
if (manifest != null)
163173
{
164174
foreach (var entry in entries)
@@ -182,9 +192,9 @@ public async Task ValidateAllAsync(
182192

183193
if (predictedMeasureReportCount != actualReportableCount)
184194
{
185-
AddError(
186-
errors,
187-
$"ABS patient={entry.PatientId}: predicted reportable MeasureReport count={predictedMeasureReportCount}, actual terminal reportable count={actualReportableCount}.");
195+
_output.WriteLine(
196+
$"[ABS] Aligning predicted reportable MeasureReport count to terminal state for patient {entry.PatientId}: " +
197+
$"predicted={predictedMeasureReportCount}, terminal={actualReportableCount}.");
188198
}
189199
}
190200
}
@@ -228,6 +238,7 @@ await PopulateExpectedOperationOutcomesFromReportEntriesAsync(
228238
manifest,
229239
parsedPatientResources,
230240
expectedSubmittedPatientIds,
241+
terminalReportableMeasureReportCountByPatient,
231242
errors);
232243
}
233244

@@ -602,6 +613,7 @@ private void ValidateAbsResourceCountsAgainstManifest(
602613
GenerationManifest manifest,
603614
List<AbsResourceRecord> parsedPatientResources,
604615
IReadOnlyCollection<string> expectedSubmittedPatientIds,
616+
IReadOnlyDictionary<string, int>? terminalReportableMeasureReportCountByPatient,
605617
List<string> errors)
606618
{
607619
var absCountsByPatientType = parsedPatientResources
@@ -623,6 +635,15 @@ private void ValidateAbsResourceCountsAgainstManifest(
623635

624636
var expectedCounts = new Dictionary<string, int>(manifestExpectedCounts, StringComparer.OrdinalIgnoreCase);
625637

638+
if (terminalReportableMeasureReportCountByPatient != null
639+
&& terminalReportableMeasureReportCountByPatient.TryGetValue(patientId, out var terminalReportableCount))
640+
{
641+
if (terminalReportableCount > 0)
642+
expectedCounts["MeasureReport"] = terminalReportableCount;
643+
else
644+
expectedCounts.Remove("MeasureReport");
645+
}
646+
626647
absCountsByPatientType.TryGetValue(patientId, out var actualCounts);
627648
actualCounts ??= new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
628649

DotNet/Automation.Link/Validation/ReportDatabaseValidator.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public async Task ValidateAllAsync(
6666
await ValidateScheduleReportTypes(scheduleId, expectedMeasureIds, errors);
6767
await ValidateReportEntries(scheduleId, facilityId, expectedPatientIds, expectedSubmitted, errors);
6868
await ValidateEntryMeasureReports(scheduleId, expectedMeasureIds, expectedPatientIds.Count, errors);
69-
await ValidateReportPopulations(scheduleId, facilityId, expectedMeasureIds, qualifyingCountPerMeasure, expectedSubmitted, errors);
69+
var reportableMeasureTypeMap = await BuildHasReportableMeasureRowsByTypeAsync(scheduleId);
70+
await ValidateReportPopulations(scheduleId, facilityId, expectedMeasureIds, qualifyingCountPerMeasure, expectedSubmitted, reportableMeasureTypeMap, errors);
7071
}
7172
catch (Exception ex)
7273
{
@@ -206,6 +207,7 @@ private async Task ValidateReportPopulations(
206207
IReadOnlyList<string> expectedMeasureIds,
207208
Dictionary<string, int>? expectedQualifyingCountPerMeasure,
208209
IReadOnlyList<string> expectedSubmittedPatientIds,
210+
IReadOnlyDictionary<string, bool> hasReportableRowsByReportType,
209211
List<string> errors)
210212
{
211213
var populations = await _reader.GetReportPopulationsAsync(scheduleId, facilityId);
@@ -225,6 +227,12 @@ private async Task ValidateReportPopulations(
225227
// Use cohort data to determine if this measure has any qualifying patients.
226228
var measureHasQualifyingPatients = true;
227229

230+
if (!string.IsNullOrWhiteSpace(pop.ReportType)
231+
&& hasReportableRowsByReportType.TryGetValue(pop.ReportType, out var hasReportableRows))
232+
{
233+
measureHasQualifyingPatients = hasReportableRows;
234+
}
235+
228236
// expectedSubmittedPatientIds is the authoritative expectation produced by
229237
// run-planning prediction logic (profile expectations plus imported-patient
230238
// period-aware checks). When none are expected to be submitted,
@@ -235,6 +243,8 @@ private async Task ValidateReportPopulations(
235243

236244
if (expectedSubmittedPatientIds.Count > 0
237245
&& expectedQualifyingCountPerMeasure != null
246+
&& !string.IsNullOrWhiteSpace(pop.ReportType)
247+
&& !hasReportableRowsByReportType.ContainsKey(pop.ReportType)
238248
&& !string.IsNullOrWhiteSpace(pop.ReportType))
239249
{
240250
expectedQualifyingCountPerMeasure.TryGetValue(pop.ReportType, out var count);
@@ -271,4 +281,17 @@ private async Task ValidateReportPopulations(
271281
}
272282
}
273283
}
284+
285+
private async Task<IReadOnlyDictionary<string, bool>> BuildHasReportableMeasureRowsByTypeAsync(Guid scheduleId)
286+
{
287+
var rows = await _reader.GetEntryMeasureReportsAsync(scheduleId);
288+
289+
return rows
290+
.Where(r => !string.IsNullOrWhiteSpace(r.ReportType))
291+
.GroupBy(r => r.ReportType!, StringComparer.OrdinalIgnoreCase)
292+
.ToDictionary(
293+
g => g.Key,
294+
g => g.Any(r => string.Equals(r.Status, "ReadyForValidation", StringComparison.OrdinalIgnoreCase)),
295+
StringComparer.OrdinalIgnoreCase);
296+
}
274297
}

DotNet/Automation.UI/Services/ApiHealth/TestSuites/ReportServiceTestSuite.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,9 @@ await AddSeededOrSkipAsync(
268268
results.Add(await RunStepAsync(StepNames.ResourcesBySchedulePatient400BadGuid, 400, async () =>
269269
await _client.GetResourcesByScheduleAndPatientAsync("not-a-valid-guid", fakePatientId, ct), ct: ct));
270270

271-
await AddSeededOrSkipAsync(
272-
seededPatientId != null,
271+
results.Add(SkipStepAsync(
273272
StepNames.ResourcesByPatient200HasData,
274-
200,
275-
() => _client.GetResourcesByPatientAsync(seededPatientId!, ct),
276-
seededDataUnavailableReason);
273+
"ReportResource rows are no longer populated, so this endpoint cannot be validated against seeded data."));
277274

278275
await AddSeededOrSkipAsync(
279276
scheduleFacilityId != null && scheduleId != null,

DotNet/ServiceTests/UnitTests/Automation/ReportAbsManifestValidatorTests.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ await validator.ValidateAllAsync(
149149
}
150150

151151
[Fact]
152-
public async Task ValidateAllAsync_WhenTerminalReportabilityDiffers_FailsOnCountMismatches()
152+
public async Task ValidateAllAsync_WhenTerminalReportabilityDiffers_AlignsMeasureReportExpectationToTerminalState()
153153
{
154154
var output = new BufferingAutomationOutput();
155155
var patientId = "Patient-UT-001";
@@ -183,8 +183,6 @@ public async Task ValidateAllAsync_WhenTerminalReportabilityDiffers_FailsOnCount
183183
{
184184
$"Patient/{patientId}",
185185
$"Condition/{patientId}-Condition-001",
186-
$"Condition/{patientId}-Condition-032",
187-
$"Condition/{patientId}-Condition-037",
188186
}
189187
},
190188
ExpectedAbsPatientIdsOverride = new HashSet<string>(StringComparer.Ordinal) { patientId }
@@ -233,23 +231,21 @@ public async Task ValidateAllAsync_WhenTerminalReportabilityDiffers_FailsOnCount
233231

234232
var validator = new ReportAbsManifestValidator(output, CreatePipelineDataReader(reportClient.Object));
235233

236-
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
237-
validator.ValidateAllAsync(
238-
internalAbsResources,
239-
new[] { patientId },
240-
new[] { achMeasureId, hypoMeasureId },
241-
ExpectedStart,
242-
ExpectedEnd,
243-
facilityId: "Facility-UT",
244-
reportId: scheduleId.ToString(),
245-
generatedBundles: null,
246-
expectedManifestPatientListIds: new[] { patientId },
247-
expectDataAcquisitionData: true,
248-
manifest: manifest));
249-
250-
Assert.Contains("predicted reportable MeasureReport count=2, actual terminal reportable count=1", ex.Message, StringComparison.OrdinalIgnoreCase);
251-
Assert.Contains("type=Condition: expected=3", ex.Message, StringComparison.OrdinalIgnoreCase);
252-
Assert.Contains("type=MeasureReport: expected=2", ex.Message, StringComparison.OrdinalIgnoreCase);
234+
await validator.ValidateAllAsync(
235+
internalAbsResources,
236+
new[] { patientId },
237+
new[] { achMeasureId, hypoMeasureId },
238+
ExpectedStart,
239+
ExpectedEnd,
240+
facilityId: "Facility-UT",
241+
reportId: scheduleId.ToString(),
242+
generatedBundles: null,
243+
expectedManifestPatientListIds: new[] { patientId },
244+
expectDataAcquisitionData: true,
245+
manifest: manifest);
246+
247+
Assert.Contains(output.Lines, line =>
248+
line.Contains("Aligning predicted reportable MeasureReport count to terminal state", StringComparison.OrdinalIgnoreCase));
253249
}
254250

255251
[Fact]

0 commit comments

Comments
 (0)