Show legacy CCS cases in interactive scene

This commit is contained in:
hz
2026-08-05 20:46:55 -05:00
parent af2bbb673d
commit 7709d16018
2 changed files with 99 additions and 5 deletions
+33
View File
@@ -177,6 +177,39 @@ public sealed class CcsCaseTests
Assert.Throws<InvalidOperationException>(() => CcsStore.ApplyDialogueChoice(attempt, definition, "ask_reason", DateTime.UtcNow));
}
[Fact]
public void LegacyCaseGetsInterviewFromExistingChartNarrative()
{
var definition = ValidImport().Cases[0];
definition.InitialInterview = new();
definition.InitialChart.HistorySections =
[
new() { Code = "hpi", Title = "History of present illness", Text = "The symptoms began this morning." },
new() { Code = "medications", Title = "Medications", Text = "I take no regular medicines." }
];
var interview = CcsStore.EffectiveInterview(definition);
var attempt = new CcsAttempt { InterviewNodeId = interview.StartNodeId, OpeningCompleted = false };
Assert.Equal("opening", interview.StartNodeId);
Assert.Equal(5, interview.Nodes.Count);
CcsStore.ApplyDialogueChoice(attempt, definition, "ask_reason", DateTime.UtcNow);
CcsStore.ApplyDialogueChoice(attempt, definition, "ask_history_1", DateTime.UtcNow);
CcsStore.ApplyDialogueChoice(attempt, definition, "ask_history_2", DateTime.UtcNow);
CcsStore.ApplyDialogueChoice(attempt, definition, "finish_interview", DateTime.UtcNow);
Assert.Equal("complete", attempt.InterviewNodeId);
Assert.True(attempt.OpeningCompleted);
}
[Fact]
public void AuthoredInterviewRemainsPreferredOverGeneratedFallback()
{
var definition = ValidImport().Cases[0];
definition.InitialInterview = ValidInterview();
Assert.Same(definition.InitialInterview, CcsStore.EffectiveInterview(definition));
}
[Fact]
public void CcsExpectedScoreUsesSeparateUserAndCaseElo()
{
+66 -5
View File
@@ -108,6 +108,7 @@ public sealed class CcsStore
}
if (definition is null) throw new KeyNotFoundException("CCS case not found.");
var now = DateTime.UtcNow;
var interview = EffectiveInterview(definition);
var attempt = new CcsAttempt
{
UserId = userId,
@@ -117,7 +118,7 @@ public sealed class CcsStore
CaseVersion = definition.Version,
Mode = CcsAttemptModes.Timed,
OpeningCompleted = false,
InterviewNodeId = definition.InitialInterview.StartNodeId,
InterviewNodeId = interview.StartNodeId,
Location = definition.PresentingLocation,
CurrentVitals = new Dictionary<string, string>(definition.InitialChart.Vitals),
VitalHistory =
@@ -415,7 +416,8 @@ public sealed class CcsStore
? attempt.VitalHistory
: [new CcsVitalSnapshot { SimulatedMinute = attempt.SimulatedMinute, Values = new Dictionary<string, string>(attempt.CurrentVitals) }];
CcsInterviewStateResponse? interview = null;
var currentInterviewNode = definition.InitialInterview.Nodes.FirstOrDefault(value =>
var interviewDefinition = EffectiveInterview(definition);
var currentInterviewNode = interviewDefinition.Nodes.FirstOrDefault(value =>
value.Id.Equals(attempt.InterviewNodeId, StringComparison.OrdinalIgnoreCase));
if (currentInterviewNode is not null)
{
@@ -458,12 +460,13 @@ public sealed class CcsStore
public static void ApplyDialogueChoice(CcsAttempt attempt, CcsCaseRevision definition, string choiceId, DateTime now)
{
if (attempt.OpeningCompleted == true) throw new InvalidOperationException("The initial interview is already complete.");
var node = definition.InitialInterview.Nodes.FirstOrDefault(value =>
var interview = EffectiveInterview(definition);
var node = interview.Nodes.FirstOrDefault(value =>
value.Id.Equals(attempt.InterviewNodeId, StringComparison.OrdinalIgnoreCase))
?? throw new InvalidOperationException("The interactive interview is unavailable.");
var choice = node.Choices.FirstOrDefault(value => value.Id.Equals(choiceId, StringComparison.OrdinalIgnoreCase))
?? throw new ArgumentException("The dialogue choice is not available at the current interview node.");
var next = definition.InitialInterview.Nodes.FirstOrDefault(value =>
var next = interview.Nodes.FirstOrDefault(value =>
value.Id.Equals(choice.NextNodeId, StringComparison.OrdinalIgnoreCase))
?? throw new InvalidOperationException("The dialogue choice target is unavailable.");
attempt.Actions.Add(Action("dialogue_choice", choice.Id, attempt.SimulatedMinute, now));
@@ -727,8 +730,66 @@ public sealed class CcsStore
ExternalId = value.ExternalId, Title = value.Title, System = value.System,
PresentingLocation = value.PresentingLocation, RealTimeMinutes = value.RealTimeMinutes,
Difficulty = value.Difficulty, Tags = value.Tags,
InteractiveInterviewAvailable = !string.IsNullOrWhiteSpace(value.InitialInterview.StartNodeId) && value.InitialInterview.Nodes.Count > 0
InteractiveInterviewAvailable = EffectiveInterview(value).Nodes.Count > 0
};
internal static CcsInitialInterview EffectiveInterview(CcsCaseRevision definition)
{
if (!string.IsNullOrWhiteSpace(definition.InitialInterview.StartNodeId)
&& definition.InitialInterview.Nodes.Count > 0)
return definition.InitialInterview;
var narrative = definition.InitialChart.HistorySections
.Where(value => !string.IsNullOrWhiteSpace(value.Text))
.Select((value, index) => new
{
Id = $"history_{index + 1}",
Prompt = string.IsNullOrWhiteSpace(value.Title)
? "Can you tell me more about your history?"
: $"Can you tell me about your {value.Title.Trim().ToLowerInvariant()}?",
value.Text
}).ToList();
if (narrative.Count == 0 && !string.IsNullOrWhiteSpace(definition.InitialChart.History))
narrative.Add(new { Id = "history_1", Prompt = "Can you tell me more about your history?", Text = definition.InitialChart.History });
var nodes = new List<CcsDialogueNode>
{
new()
{
Id = "opening", PatientText = "Hello, doctor.",
Choices = [new() { Id = "ask_reason", Text = "What brings you in today?", NextNodeId = "reason" }]
},
new()
{
Id = "reason", PatientText = definition.InitialChart.ReasonForEncounter,
Choices = [new()
{
Id = narrative.Count > 0 ? "ask_history_1" : "finish_interview",
Text = narrative.Count > 0 ? narrative[0].Prompt : "Is there anything else you would like me to know?",
NextNodeId = narrative.Count > 0 ? narrative[0].Id : "complete"
}]
}
};
for (var index = 0; index < narrative.Count; index++)
{
var next = index + 1 < narrative.Count ? narrative[index + 1] : null;
nodes.Add(new CcsDialogueNode
{
Id = narrative[index].Id, PatientText = narrative[index].Text,
Choices = [new()
{
Id = next is null ? "finish_interview" : $"ask_history_{index + 2}",
Text = next?.Prompt ?? "Is there anything else you would like me to know?",
NextNodeId = next?.Id ?? "complete"
}]
});
}
nodes.Add(new CcsDialogueNode
{
Id = "complete", PatientText = "That is everything I can think of, doctor.", IsTerminal = true
});
return new CcsInitialInterview { StartNodeId = "opening", Nodes = nodes };
}
internal static int Rank(CcsOrderDefinition order, string query)
{
var values = new[] { order.CanonicalName }.Concat(order.Aliases).Select(value => value.ToLowerInvariant());