Add graphical duodenal ulcer interview

This commit is contained in:
hz
2026-08-05 23:14:51 -05:00
parent 7709d16018
commit 58dd370149
14 changed files with 768 additions and 29 deletions
+83
View File
@@ -91,6 +91,18 @@ public sealed class CcsCaseTests
Assert.True(request!.Cases.Single().ScoringCriteria.Count >= 12);
}
[Fact]
public void DuodenalUlcerInterviewArtifactMatchesTheCaseSchema()
{
var path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../content/ccs/duodenal-ulcer-office-001.json"));
var request = JsonSerializer.Deserialize<CcsImportRequest>(File.ReadAllText(path), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
Assert.NotNull(request);
Assert.Empty(CcsCaseValidator.ValidateImport(request!, new HashSet<string>()));
Assert.Equal(CcsExperienceModes.GraphicalInterview, request.Cases.Single().ExperienceMode);
Assert.Equal(4, request.Cases.Single().InitialInterview.Topics.Count(value => value.Required));
}
[Fact]
public void PublishedCaseRequiresReferencesAndKnownOrders()
{
@@ -210,6 +222,58 @@ public sealed class CcsCaseTests
Assert.Same(definition.InitialInterview, CcsStore.EffectiveInterview(definition));
}
[Fact]
public void TopicInterviewBranchesAndUnlocksFinishAfterRequiredTopics()
{
var definition = ValidImport().Cases[0];
definition.InitialInterview = TopicInterview();
var attempt = new CcsAttempt { InterviewNodeId = "opening", OpeningCompleted = false };
Assert.Equal(["chief_concern"], CcsStore.CurrentInterviewNode(attempt, definition)!.Choices.Select(value => value.Id));
Assert.Throws<ArgumentException>(() => CcsStore.ApplyDialogueChoice(attempt, definition, "required_history", DateTime.UtcNow));
CcsStore.ApplyDialogueChoice(attempt, definition, "chief_concern", DateTime.UtcNow);
Assert.Equal(["required_history", "optional_history"],
CcsStore.CurrentInterviewNode(attempt, definition)!.Choices.Select(value => value.Id));
Assert.Throws<ArgumentException>(() => CcsStore.ApplyDialogueChoice(attempt, definition, "finish_interview", DateTime.UtcNow));
CcsStore.ApplyDialogueChoice(attempt, definition, "required_history", DateTime.UtcNow);
var finishable = CcsStore.CurrentInterviewNode(attempt, definition)!;
Assert.Contains(finishable.Choices, value => value.Id == "finish_interview");
Assert.DoesNotContain(finishable.Choices, value => value.Id == "chief_concern");
Assert.Throws<ArgumentException>(() => CcsStore.ApplyDialogueChoice(attempt, definition, "chief_concern", DateTime.UtcNow));
CcsStore.ApplyDialogueChoice(attempt, definition, "finish_interview", DateTime.UtcNow);
Assert.True(attempt.OpeningCompleted);
Assert.True(CcsStore.CurrentInterviewNode(attempt, definition)!.IsTerminal);
}
[Fact]
public void TopicInterviewRejectsPrerequisiteCycles()
{
var request = ValidImport();
request.Cases[0].InitialInterview = TopicInterview();
request.Cases[0].InitialInterview.Topics[0].PrerequisiteTopicIds = ["required_history"];
var errors = CcsCaseValidator.ValidateImport(request, new HashSet<string>());
Assert.Contains(errors, value => value.Contains("prerequisites cannot contain cycles", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void CaseContentComparisonIgnoresRevisionMetadata()
{
var first = ValidImport().Cases[0];
var second = ValidImport().Cases[0];
first.Id = "507f1f77bcf86cd799439011";
first.Version = 4;
first.IsCurrent = false;
first.CreatedUtc = DateTime.UtcNow.AddDays(-2);
second.InitialChart.SimulatedStartUtc = first.InitialChart.SimulatedStartUtc;
Assert.Equal(CcsStore.CaseContentDocument(first), CcsStore.CaseContentDocument(second));
second.Title = "Changed title";
Assert.NotEqual(CcsStore.CaseContentDocument(first), CcsStore.CaseContentDocument(second));
}
[Fact]
public void CcsExpectedScoreUsesSeparateUserAndCaseElo()
{
@@ -286,4 +350,23 @@ public sealed class CcsCaseTests
new() { Id = "done", PatientText = "Thank you.", IsTerminal = true }
]
};
private static CcsInitialInterview TopicInterview() => new()
{
OpeningPatientText = "Good morning.", ClosingPatientText = "Thank you.", FinishChoiceText = "Finish",
Topics =
[
new() { Id = "chief_concern", ClinicianText = "What brings you in?", PatientText = "I feel tired." },
new()
{
Id = "required_history", ClinicianText = "Tell me more.", PatientText = "It started recently.", Required = true,
PrerequisiteTopicIds = ["chief_concern"]
},
new()
{
Id = "optional_history", ClinicianText = "Anything else?", PatientText = "No.",
PrerequisiteTopicIds = ["chief_concern"]
}
]
};
}